【问题标题】:SQL-like window functions in PANDAS: Row Numbering in Python Pandas DataframePANDAS 中类似 SQL 的窗口函数:Python Pandas Dataframe 中的行编号
【发布时间】:2013-07-20 12:01:16
【问题描述】:

我来自sql背景,我经常使用以下数据处理步骤:

  1. 按一个或多个字段对数据表进行分区
  2. 对于每个分区,将行号添加到其每一行,该行按一个或多个其他字段对行进行排名,分析师指定升序或降序

前:

df = pd.DataFrame({'key1' : ['a','a','a','b','a'],
           'data1' : [1,2,2,3,3],
           'data2' : [1,10,2,3,30]})
df
     data1        data2     key1    
0    1            1         a           
1    2            10        a        
2    2            2         a       
3    3            3         b       
4    3            30        a        

我正在寻找如何做相当于这个sql窗口函数的PANDAS:

RN = ROW_NUMBER() OVER (PARTITION BY Key1 ORDER BY Data1 ASC, Data2 DESC)


    data1        data2     key1    RN
0    1            1         a       1    
1    2            10        a       2 
2    2            2         a       3
3    3            3         b       1
4    3            30        a       4

我已经尝试了以下没有“分区”的工作:

def row_number(frame,orderby_columns, orderby_direction,name):
    frame.sort_index(by = orderby_columns, ascending = orderby_direction, inplace = True)
    frame[name] = list(xrange(len(frame.index)))

我试图将这个想法扩展到使用分区(pandas 中的组),但以下方法不起作用:

df1 = df.groupby('key1').apply(lambda t: t.sort_index(by=['data1', 'data2'], ascending=[True, False], inplace = True)).reset_index()

def nf(x):
    x['rn'] = list(xrange(len(x.index)))

df1['rn1'] = df1.groupby('key1').apply(nf)

但是当我这样做时,我得到了很多 NaN。

理想情况下,会有一种简洁的方法来复制 sql 的窗口函数功能(我已经想出了基于窗口的聚合...这是 pandas 中的一条线)...有人可以与我分享最多在 PANDAS 中像这样对行进行编号的惯用方法?

【问题讨论】:

  • 看来你应该可以多栏.rank...

标签: python pandas numpy dataframe


【解决方案1】:

使用 groupby.rank 函数。 这是工作示例。

df = pd.DataFrame({'C1':['a', 'a', 'a', 'b', 'b'], 'C2': [1, 2, 3, 4, 5]})
df

C1 C2
a  1
a  2
a  3
b  4
b  5

df["RANK"] = df.groupby("C1")["C2"].rank(method="first", ascending=True)
df

C1 C2 RANK
a  1  1
a  2  2
a  3  3
b  4  1
b  5  2

【讨论】:

  • 对一个老问题的优雅的最新答案。
  • 如果您只需要一列来排序,而不需要多列(这就是接受的答案更复杂的原因),这是一个很好的解决方案。
【解决方案2】:

你可以同时使用transformRank这里是一个例子

df = pd.DataFrame({'C1' : ['a','a','a','b','b'],
           'C2' : [1,2,3,4,5]})
df['Rank'] = df.groupby(by=['C1'])['C2'].transform(lambda x: x.rank())
df

查看 Pandas Rank 方法了解更多信息

【讨论】:

  • 这与 Pandas 中的类似 SQL 的窗口功能一样接近。也可以只传入 pandas Rank 函数,而不是将其包装在 lambda 中。 df.groupby(by=['C1'])['C2'].transform(pd.DataFrame.rank)
  • 要获得row_number() 的行为,您应该将method='first' 传递给rank 函数。同样,对于rank()dense_rank(),您应该分别传递method='min'method='dense'。顺便说一句,这实际上就是为什么这里需要一个 lambda 函数@timle
【解决方案3】:

你也可以使用sort_values()groupby(),最后是cumcount() + 1

df['RN'] = df.sort_values(['data1','data2'], ascending=[True,False]) \
             .groupby(['key1']) \
             .cumcount() + 1
print(df)

产量:

   data1  data2 key1  RN
0      1      1    a   1
1      2     10    a   2
2      2      2    a   3
3      3      3    b   1
4      3     30    a   4

使用 pandas 0.18 进行 PS 测试

【讨论】:

  • 刚刚尝试了这个方法,得到了以下警告:SettingWithCopyWarning: A value is trying to be set on a copy of a slice from DataFrame。尝试改用 .loc[row_indexer,col_indexer] = value
  • @AndrewL,您的 df 似乎是“DF 切片的复制副本”...以下命令是否适用于您的 dfdf['new'] = 0 没有 这个警告?
  • @maxU 如果您在 groupby 排序发生之前应用 sort_values() 而与“key1”无关。但在原始窗口函数中,期望是在 'key1' 的键空间内排序
  • 获取“ValueError:无法从重复轴重新索引”。有什么帮助吗?
  • @MaxU 我已经创建了这个stackoverflow.com/questions/55421932/… 任何帮助将不胜感激
【解决方案4】:

pandas.lib.fast_zip() 可以从数组列表中创建一个元组数组。您可以使用此函数创建一个元组系列,然后对其进行排名:

values = {'key1' : ['a','a','a','b','a','b'],
          'data1' : [1,2,2,3,3,3],
          'data2' : [1,10,2,3,30,20]}

df = pd.DataFrame(values, index=list("abcdef"))

def rank_multi_columns(df, cols, **kw):
    data = []
    for col in cols:
        if col.startswith("-"):
            flag = -1
            col = col[1:]
        else:
            flag = 1
        data.append(flag*df[col])
    values = pd.lib.fast_zip(data)
    s = pd.Series(values, index=df.index)
    return s.rank(**kw)

rank = df.groupby("key1").apply(lambda df:rank_multi_columns(df, ["data1", "-data2"]))

print rank

结果:

a    1
b    2
c    3
d    2
e    4
f    1
dtype: float64

【讨论】:

    【解决方案5】:

    您可以通过使用groupby 两次以及rank 方法来做到这一点:

    In [11]: g = df.groupby('key1')
    

    使用 min 方法参数将共享相同数据的值赋予相同的 RN:

    In [12]: g['data1'].rank(method='min')
    Out[12]:
    0    1
    1    2
    2    2
    3    1
    4    4
    dtype: float64
    
    In [13]: df['RN'] = g['data1'].rank(method='min')
    

    然后将这些结果分组并添加关于data2的排名:

    In [14]: g1 = df.groupby(['key1', 'RN'])
    
    In [15]: g1['data2'].rank(ascending=False) - 1
    Out[15]:
    0    0
    1    0
    2    1
    3    0
    4    0
    dtype: float64
    
    In [16]: df['RN'] += g1['data2'].rank(ascending=False) - 1
    
    In [17]: df
    Out[17]:
       data1  data2 key1  RN
    0      1      1    a   1
    1      2     10    a   2
    2      2      2    a   3
    3      3      3    b   1
    4      3     30    a   4
    

    感觉应该有一种本地方式来做到这一点(很可能!...)。

    【讨论】:

    • 我同意,多列排名似乎很自然...我应该在 github 上请求它吗?
    • @AndyHayden 要获得row_number() 的行为,你应该传递method='first',而不是method='min'(这会给你rank() 行为)。
    猜你喜欢
    • 2016-04-16
    • 2021-04-05
    • 2016-01-31
    • 2023-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-25
    • 1970-01-01
    相关资源
    最近更新 更多