Pandas - rank() 函数的替代方法,它为列提供唯一的序数排名

Pandas - Alternative to rank() function that gives unique ordinal ranks for a column

此刻我正在编写一个 Python 脚本,用于聚合来自多个 Excel 工作表的数据。我选择使用的模块是 Pandas,因为它的速度快且易于使用 Excel 文件。这个问题只与 Pandas 的使用有关,我试图创建一个额外的列,其中包含 唯一的、仅整数、序数 组内的排名。

我的 Python 和 Pandas 知识有限,因为我只是一个初学者。

目标

我正在尝试实现以下数据结构。其中排名前 10 位的 adwords 广告根据它们在 Google 中的位置进行垂直排名。为此,我需要在原始数据中创建一个列(请参阅 Table 2 和 3),其中包含不包含重复值的仅整数排名。

Table 1:我要实现的数据结构

    device  , weeks   , rank_1   , rank_2   , rank_3   , rank_4   , rank_5
    mobile  , wk 1    , string   , string   , string   , string   , string 
    mobile  , wk 2    , string   , string   , string   , string   , string 
    computer, wk 1    , string   , string   , string   , string   , string
    computer, wk 2    , string   , string   , string   , string   , string

问题

我 运行 遇到的确切问题是无法有效地对具有 pandas 的行进行排名。我尝试了很多东西,但我似乎无法以这种方式对其进行排名。

Table2:数据结构我有

    weeks    device   , website  , ranking  , adtext
    wk 1     mobile   , url1     , *2.1     , string
    wk 1     mobile   , url2     , *2.1     , string
    wk 1     mobile   , url3     , 1.0      , string
    wk 1     mobile   , url4     , 2.9      , string
    wk 1     desktop  , *url5    , 2.1      , string
    wk 1     desktop  , url2     , *1.5     , string
    wk 1     desktop  , url3     , *1.5     , string
    wk 1     desktop  , url4     , 2.9      , string
    wk 2     mobile   , url1     , 2.0      , string
    wk 2     mobile   , *url6    , 2.1      , string
    wk 2     mobile   , url3     , 1.0      , string
    wk 2     mobile   , url4     , 2.9      , string
    wk 2     desktop  , *url5    , 2.1      , string
    wk 2     desktop  , url2     , *2.9     , string
    wk 2     desktop  , url3     , 1.0      , string
    wk 2     desktop  , url4     , *2.9     , string

Table 3: table 我似乎无法创建

    weeks    device   , website  , ranking  , adtext  , ranking
    wk 1     mobile   , url1     , *2.1     , string  , 2
    wk 1     mobile   , url2     , *2.1     , string  , 3
    wk 1     mobile   , url3     , 1.0      , string  , 1
    wk 1     mobile   , url4     , 2.9      , string  , 4
    wk 1     desktop  , *url5    , 2.1      , string  , 3
    wk 1     desktop  , url2     , *1.5     , string  , 1
    wk 1     desktop  , url3     , *1.5     , string  , 2
    wk 1     desktop  , url4     , 2.9      , string  , 4
    wk 2     mobile   , url1     , 2.0      , string  , 2
    wk 2     mobile   , *url6    , 2.1      , string  , 3
    wk 2     mobile   , url3     , 1.0      , string  , 1
    wk 2     mobile   , url4     , 2.9      , string  , 4
    wk 2     desktop  , *url5    , 2.1      , string  , 2
    wk 2     desktop  , url2     , *2.9     , string  , 3
    wk 2     desktop  , url3     , 1.0      , string  , 1
    wk 2     desktop  , url4     , *2.9     , string  , 4

标准 .rank(ascending=True),给出重复值的平均值。但是由于我使用这些等级来垂直组织它们,所以这行不通。

df = df.sort_values(['device', 'weeks', 'ranking'], ascending=[True, True, True])

df['newrank'] = df.groupby(['device', 'week'])['ranking'].rank( ascending=True)

.rank(method="dense", ascending=True) 保持重复值,也没有解决我的问题

df = df.sort_values(['device', 'weeks', 'ranking'], ascending=[True, True, True])

df['newrank'] = df.groupby(['device', 'week'])['ranking'].rank( method="dense", ascending=True)

.rank(method="first", ascending=True) 抛出 ValueError

df = df.sort_values(['device', 'weeks', 'ranking'], ascending=[True, True, True])

df['newrank'] = df.groupby(['device', 'week'])['ranking'].rank( method="first", ascending=True)

附录:如果我能找到一种在列中添加排名的方法,我会使用 pivot 按以下方式转置 table。

df = pd.pivot_table(df, index = ['device', 'weeks'], columns='website', values='adtext', aggfunc=lambda x: ' '.join(x))

我给你的问题

我希望你们能帮我找到解决这个问题的办法。这可能是一个有效的排名脚本或其他帮助我达到最终数据结构的东西。

谢谢!

塞巴斯蒂安


编辑:不幸的是,我想我在原来的 post 中并不清楚。我正在寻找只给出整数且没有重复值的序数排名。这意味着当存在重复值时,它会随机给一个比另一个更高的排名。

所以我想做的是生成一个排名,用每组的序数值标记每一行。这些组基于周数和设备。我想用这个排名创建一个新专栏的原因是我可以每周和设备进入前 10。

Steven G 也问我要一个例子来玩玩。我已经在这里提供了。

示例数据可以直接粘贴到python

!重要提示:此示例中的名称不同。数据框称为占位符,列名如下:'week'、'website'、'share'、'rank_google'、'device'。

data = {u'week': [u'WK 1', u'WK 2', u'WK 3', u'WK 4', u'WK 2', u'WK 2', u'WK 1',
u'WK 3', u'WK 4', u'WK 3', u'WK 3', u'WK 4', u'WK 2', u'WK 4', u'WK 1', u'WK 1',
u'WK3', u'WK 4', u'WK 4', u'WK 4', u'WK 4', u'WK 2', u'WK 1', u'WK 4', u'WK 4',
u'WK 4', u'WK 4', u'WK 2', u'WK 3', u'WK 4', u'WK 3', u'WK 4', u'WK 3', u'WK 2',
u'WK 2', u'WK 4', u'WK 1', u'WK 1', u'WK 4', u'WK 4', u'WK 2', u'WK 1', u'WK 3',
u'WK 1', u'WK 4', u'WK 1', u'WK 4', u'WK 2', u'WK 2', u'WK 2', u'WK 4', u'WK 4',
u'WK 4', u'WK 1', u'WK 3', u'WK 4', u'WK 4', u'WK 1', u'WK 4', u'WK 3', u'WK 2',
u'WK 4', u'WK 4', u'WK 4', u'WK 4', u'WK 1'],
u'website': [u'site1.nl', u'website2.de', u'site1.nl', u'site1.nl', u'anothersite.com',
u'url2.at', u'url2.at', u'url2.at', u'url2.at', u'anothersite.com', u'url2.at',
u'url2.at', u'url2.at', u'url2.at', u'url2.at', u'anothersite.com', u'url2.at',
u'url2.at', u'url2.at', u'url2.at', u'anothersite.com', u'url2.at', u'url2.at',
u'anothersite.com', u'site2.co.uk', u'sitename2.com', u'sitename.co.uk', u'sitename.co.uk',
u'sitename2.com', u'sitename2.com', u'sitename2.com', u'url3.fi', u'sitename.co.uk',
u'sitename2.com', u'sitename.co.uk', u'sitename2.com', u'sitename2.com', u'ulr2.se',
u'sitename2.com', u'sitename.co.uk', u'sitename2.com', u'sitename2.com', u'sitename2.com',
u'sitename2.com', u'sitename2.com', u'sitename.co.uk', u'sitename.co.uk', u'sitename2.com',
u'facebook.com', u'alsoasite.com', u'ello.com', u'instagram.com', u'alsoasite.com', u'facebook.com',
u'facebook.com', u'singleboersen-vergleich.at', u'facebook.com', u'anothername.com', u'twitter.com',
u'alsoasite.com', u'alsoasite.com', u'alsoasite.com', u'alsoasite.com', u'facebook.com', u'alsoasite.com',
u'alsoasite.com'],
'adtext': [u'site1.nl 3,9 | < 10\xa0%', u'website2.de 1,4 | < 10\xa0%', u'site1.nl 4,3 | < 10\xa0%',
u'site1.nl 3,8 | < 10\xa0%', u'anothersite.com 2,5 | 12,36 %', u'url2.at 1,3 | 78,68 %', u'url2.at 1,2 | 92,58 %',
u'url2.at 1,1 | 85,47 %', u'url2.at 1,2 | 79,56 %', u'anothersite.com 2,8 | < 10\xa0%', u'url2.at 1,2 | 80,48 %',
u'url2.at 1,2 | 85,63 %', u'url2.at 1,1 | 88,36 %', u'url2.at 1,3 | 87,90 %', u'url2.at 1,1 | 83,70 %',
u'anothersite.com 3,1 | < 10\xa0%', u'url2.at 1,2 | 91,00 %', u'url2.at 1,1 | 92,11 %', u'url2.at 1,2 | 81,28 %'
, u'url2.at 1,1 | 86,49 %', u'anothersite.com 2,7 | < 10\xa0%', u'url2.at 1,2 | 83,96 %', u'url2.at 1,2 | 75,48 %'
, u'anothersite.com 3,0 | < 10\xa0%', u'site2.co.uk 3,1 | 16,24 %', u'sitename2.com 2,3 | 34,85 %',
u'sitename.co.uk 3,5 | < 10\xa0%', u'sitename.co.uk 3,6 | < 10\xa0%', u'sitename2.com 2,1 | < 10\xa0%',
u'sitename2.com 2,2 | 13,55 %', u'sitename2.com 2,1 | 47,91 %', u'url3.fi 3,4 | < 10\xa0%',
u'sitename.co.uk 3,1 | 14,15 %', u'sitename2.com 2,4 | 28,77 %', u'sitename.co.uk 3,1 | 22,55 %',
u'sitename2.com 2,1 | 17,03 %', u'sitename2.com 2,1 | 24,46 %', u'ulr2.se 2,7 | < 10\xa0%',
u'sitename2.com 2,0 | 49,12 %', u'sitename.co.uk 3,0 | < 10\xa0%', u'sitename2.com 2,1 | 40,00 %',
u'sitename2.com 2,1 | < 10\xa0%', u'sitename2.com 2,2 | 30,29 %', u'sitename2.com 2,0 |47,48 %',
u'sitename2.com 2,1 | 32,17 %', u'sitename.co.uk 3,2 | < 10\xa0%', u'sitename.co.uk 3,1 | 12,77 %',
u'sitename2.com 2,6 | < 10\xa0%', u'facebook.com 3,2 | < 10\xa0%', u'alsoasite.com 2,3 | < 10\xa0%',
u'ello.com 1,8 | < 10\xa0%',u'instagram.com 5,0 | < 10\xa0%', u'alsoasite.com 2,2 | < 10\xa0%',
u'facebook.com 3,0 | < 10\xa0%', u'facebook.com 3,2 | < 10\xa0%', u'singleboersen-vergleich.at 2,6 | < 10\xa0%',
u'facebook.com 3,4 | < 10\xa0%', u'anothername.com 1,9 | <10\xa0%', u'twitter.com 4,4 | < 10\xa0%',
u'alsoasite.com 1,1 | 12,35 %', u'alsoasite.com 1,1 | 11,22 %', u'alsoasite.com 2,0 | < 10\xa0%',
u'alsoasite.com 1,1| 10,86 %', u'facebook.com 3,4 | < 10\xa0%', u'alsoasite.com 1,1 | 10,82 %',
u'alsoasite.com 1,1 | < 10\xa0%'],
u'share': [u'< 10\xa0%', u'< 10\xa0%', u'< 10\xa0%', u'< 10\xa0%', u'12,36 %', u'78,68 %',
u'92,58 %', u'85,47 %', u'79,56 %', u'< 10\xa0%', u'80,48 %', u'85,63 %', u'88,36 %',
u'87,90 %', u'83,70 %', u'< 10\xa0%', u'91,00 %', u'92,11 %', u'81,28 %', u'86,49 %',
u'< 10\xa0%', u'83,96 %', u'75,48 %', u'< 10\xa0%', u'16,24 %', u'34,85 %', u'< 10\xa0%',
u'< 10\xa0%', u'< 10\xa0%', u'13,55 %', u'47,91 %', u'< 10\xa0%', u'14,15 %', u'28,77 %',
u'22,55 %', u'17,03 %', u'24,46 %', u'< 10\xa0%', u'49,12 %', u'< 10\xa0%', u'40,00 %',
u'< 10\xa0%', u'30,29 %', u'47,48 %', u'32,17 %', u'< 10\xa0%', u'12,77 %', u'< 10\xa0%',
u'< 10\xa0%', u'< 10\xa0%', u'< 10\xa0%', u'< 10\xa0%', u'< 10\xa0%', u'< 10\xa0%', u'< 10\xa0%',
u'< 10\xa0%', u'< 10\xa0%', u'< 10\xa0%', u'< 10\xa0%', u'12,35 %', u'11,22 %', u'< 10\xa0%',
u'10,86 %', u'< 10\xa0%', u'10,82 %', u'< 10\xa0%'],
u'rank_google': [u'3,9', u'1,4', u'4,3', u'3,8', u'2,5', u'1,3', u'1,2', u'1,1', u'1,2', u'2,8',
u'1,2', u'1,2', u'1,1', u'1,3', u'1,1', u'3,1', u'1,2', u'1,1', u'1,2', u'1,1', u'2,7', u'1,2',
u'1,2', u'3,0', u'3,1', u'2,3', u'3,5', u'3,6', u'2,1', u'2,2', u'2,1', u'3,4', u'3,1', u'2,4',
u'3,1', u'2,1', u'2,1', u'2,7', u'2,0', u'3,0', u'2,1', u'2,1', u'2,2', u'2,0', u'2,1', u'3,2',
u'3,1', u'2,6', u'3,2', u'2,3', u'1,8', u'5,0', u'2,2', u'3,0', u'3,2', u'2,6', u'3,4', u'1,9',
u'4,4', u'1,1', u'1,1', u'2,0', u'1,1', u'3,4', u'1,1', u'1,1'],
u'device': [u'Mobile', u'Tablet', u'Mobile', u'Mobile', u'Tablet', u'Mobile', u'Tablet', u'Computer',
u'Mobile', u'Tablet', u'Mobile', u'Computer', u'Tablet', u'Tablet', u'Computer', u'Tablet', u'Tablet',
u'Tablet', u'Mobile', u'Computer', u'Tablet', u'Computer', u'Mobile', u'Tablet', u'Tablet', u'Mobile',
u'Tablet', u'Mobile', u'Computer', u'Computer', u'Tablet', u'Mobile', u'Tablet', u'Mobile', u'Tablet',
u'Mobile', u'Mobile', u'Mobile', u'Tablet', u'Computer', u'Tablet', u'Computer', u'Mobile', u'Tablet',
u'Tablet', u'Tablet', u'Mobile', u'Computer', u'Mobile', u'Computer', u'Tablet', u'Tablet', u'Tablet',
u'Mobile', u'Mobile', u'Tablet', u'Mobile', u'Mobile', u'Tablet', u'Mobile', u'Mobile', u'Computer',
u'Mobile', u'Tablet', u'Mobile', u'Mobile']}

placeholder = pd.DataFrame(data)

当我将 rank() 函数与方法一起使用时收到错误='first'

C:\Users\username\code\report-creator>python recomp-report-04.py
Traceback (most recent call last):
  File "recomp-report-04.py", line 71, in <module>
    placeholder['ranking'] = placeholder.groupby(['week', 'device'])['rank_googl
e'].rank(method='first').astype(int)
  File "<string>", line 35, in rank
  File "C:\Users\sthuis\AppData\Local\Continuum\Anaconda2\lib\site-packages\pand
as\core\groupby.py", line 561, in wrapper
    raise ValueError
ValueError

我的解决方案

实际上,@Nickil Maveli 给出了答案。非常感谢!尽管如此,我认为概述我最终如何合并解决方案可能是明智的。

Rank(method='first') 是获得序数排名的好方法。但是由于我正在处理以欧洲方式格式化的数字,pandas 将它们解释为字符串并且无法以这种方式对它们进行排名。我根据 Nickil Maveli 的反应得出了这个结论,并试图对每个组进行单独排名。我通过以下代码做到了。

for name, group in df.sort_values(by='rank_google').groupby(['weeks', 'device']):
    df['new_rank'] = group['ranking'].rank(method='first').astype(int)

这给了我以下错误:

ValueError: first not supported for non-numeric data

所以这帮助我意识到我应该将列转换为浮点数。我就是这样做的。

# Converting the ranking column to a float
df['ranking'] = df['ranking'].apply(lambda x: float(unicode(x.replace(',','.'))))

# Creating a new column with a rank
df['new_rank'] = df.groupby(['weeks', 'device'])['ranking'].rank(method='first').astype(int)

# Dropping all ranks after the 10
df = df.sort_values('new_rank').groupby(['weeks', 'device']).head(n=10)

# Pivotting the column
df = pd.pivot_table(df, index = ['device', 'weeks'], columns='new_rank', values='adtext', aggfunc=lambda x: ' '.join(x))

# Naming the columns with 'top' + number
df.columns = ['top ' + str(i) for i in list(df.columns.values)]

所以这对我有用。谢谢大家!

我认为您在排序后尝试使用 method=first 对它们进行排名的方式导致了问题。

您可以简单地在分组对象本身上使用带有 first arg 的排名方法,为您提供所需的每组唯一排名。

df['new_rank'] = df.groupby(['weeks','device'])['ranking'].rank(method='first').astype(int)
print (df['new_rank'])

0     2
1     3
2     1
3     4
4     3
5     1
6     2
7     4
8     2
9     3
10    1
11    4
12    2
13    3
14    1
15    4
Name: new_rank, dtype: int32

执行主元运算:

df = df.pivot_table(index=['weeks', 'device'], columns=['new_rank'],
                    values=['adtext'], aggfunc=lambda x: ' '.join(x))

选择与排名数字相关的多索引列的第二级:

df.columns = ['rank_' + str(i) for i in df.columns.get_level_values(1)]
df


数据:(复制)

df = pd.DataFrame({'weeks': ['wk 1', 'wk 1', 'wk 1', 'wk 1', 'wk 1', 'wk 1', 'wk 1', 'wk 1',
                             'wk 2', 'wk 2', 'wk 2', 'wk 2', 'wk 2', 'wk 2', 'wk 2', 'wk 2'],
                  'device': ['mobile', 'mobile', 'mobile', 'mobile', 'desktop', 'desktop', 'desktop', 'desktop',
                             'mobile', 'mobile', 'mobile', 'mobile', 'desktop', 'desktop', 'desktop', 'desktop'],
                  'website': ['url1', 'url2', 'url3', 'url4', 'url5', 'url2', 'url3', 'url4',
                             'url1', 'url16', 'url3', 'url4', 'url5', 'url2', 'url3', 'url4'],
                  'ranking': [2.1, 2.1, 1.0, 2.9, 2.1, 1.5, 1.5, 2.9, 
                              2.0, 2.1, 1.0, 2.9, 2.1, 2.9, 1.0, 2.9],
                  'adtext': ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string',
                             'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string']})

注意:method=first 按照它们在 array/series 中出现的顺序分配排名。