2013-07-21 25 views
17

Tôi đến từ một nền sql và tôi sử dụng các bước xử lý dữ liệu sau đây thường xuyên:chức năng cửa sổ SQL giống như ở gấu trúc: Row đánh số bằng Python Pandas Dataframe

  1. phân vùng bảng dữ liệu bằng một hoặc nhiều lĩnh vực
  2. Đối với mỗi phân vùng, thêm một rownumber cho mỗi hàng của mình rằng xếp hàng bởi một hoặc nhiều lĩnh vực khác, nơi mà các nhà phân tích xác định tăng dần hoặc giảm dần

EX:

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   

Tôi đang tìm cách làm gấu trúc tương đương với chức năng này cửa sổ sql:

RN = ROW_NUMBER() OVER (PARTITION BY Key1, Key2 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 

Tôi đã thử những điều sau đây mà tôi đã nhận được để làm việc mà không có 'phân vùng':

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))) 

tôi cố gắng để mở rộng ý tưởng này để làm việc với các phân vùng (nhóm trong gấu trúc) nhưng sau không làm việc:

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) 

Nhưng tôi chỉ có rất nhiều NaN khi tôi làm điều này.

Lý tưởng nhất, sẽ có một cách gọn gàng để nhân rộng khả năng cửa sổ chức năng của sql (tôi đã tìm ra tập hợp dựa trên cửa sổ ... đó là một lớp lót trong gấu trúc) ... ai đó có thể chia sẻ với tôi cách thành ngữ nhất để số hàng như thế này trong PANDAS?

+0

có vẻ như anh nên để có thể ' .rank' bởi nhiều cột ... –

Trả lời

8

Bạn có thể làm điều này bằng cách sử dụng groupby hai lần cùng với các phương pháp rank:

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

Sử dụng đối số phương pháp phút để cung cấp cho các giá trị mà chia sẻ cùng một data1 RN cùng:

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') 

Và sau đó nhóm các kết quả này và thêm xếp hạng theo dữ liệu2:

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 

Nó cảm thấy như có phải là một cách bản địa để làm điều này (có thể cũng được! ...).

+0

tôi đồng ý, xếp hạng theo nhiều cột có vẻ tự nhiên ... tôi có nên yêu cầu nó trên github không? – AllenQ

+0

cũng cảm ơn rất nhiều vì cách giải quyết khác! – AllenQ

+0

@AllenQ đã làm https://github.com/pydata/pandas/issues/4311 :) –

-1

pandas.lib.fast_zip() có thể tạo mảng tuple từ danh sách mảng. Bạn có thể sử dụng chức năng này để tạo ra một loạt tuple, và sau đó xếp hạng nó:

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 

kết quả:

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

bạn cũng có thể sử dụng sort_values(), groupby() và cuối cùng cumcount() + 1:

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

sản lượng:

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 

PS thử nghiệm với gấu trúc 0.18

+0

Chỉ cần thử phương pháp này và nhận được cảnh báo sau đây: SettingWithCopyWarning: Giá trị đang cố gắng được đặt trên bản sao của một lát từ một DataFrame. Hãy thử sử dụng .loc [row_indexer, col_indexer] = value thay vì –

+0

@AndrewL, 'df' của bạn có vẻ là" bản sao của một lát của DF "... lệnh sau hoạt động cho' df': ' df ['new'] = 0' __without__ cảnh báo này? – MaxU

+0

cảm ơn thông tin chi tiết của bạn. Lệnh trên thực sự không hoạt động mà không ném cảnh báo. Bạn có thể giải thích như thế nào "bản sao của một phần của một DF" là có vấn đề và tại sao nó được xử lý khác với một df thường xuyên? Cảm ơn bạn! –

0

Bạn có thể sử dụng transformRank cùng Dưới đây là một ví dụ

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 

enter image description here

Có xem xét phương pháp Pandas Rank để biết thêm thông tin