2013-07-21 6 views
17

Pochodzę z sql tle i używam następujący etap przetwarzania danych częściej:SQL-podobne funkcje okna w pand: numeracja rzędów w Pythonie pandy Dataframe

  1. partycji tabela danych przez jednego lub więcej pól
  2. Dla każdej partycji, dodać RowNumber do każdego z jego wierszy, które plasuje wiersz przez jednego lub kilku innych dziedzinach, gdzie analityk Określa rosnąco lub malejąco

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   

szukam jak zrobić równowartość pandy do tego sql okno funkcji:

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 

Próbowałem po których stałam się do pracy, gdzie nie ma „przegródki”:

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

starałem się rozszerzyć ten pomysł, aby pracować z partycjami (grup pand), ale dodaje nie działa:

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) 

Ale po prostu mam dużo NaN, kiedy to robię.

Idealnie byłoby przedstawić sposób przedstawienia funkcji okna funkcji sql (zrozumiałem agregaty oparte na oknach ... to jedna linijka w pandach) ... czy ktoś może mi udostępnić najbardziej idiomatyczny sposób numerowania takich wierszy w PANDAS?

+0

Wygląda na to, że powinieneś być w stanie ' .rank' przez wiele kolumn ... –

Odpowiedz

8

Można to zrobić za pomocą groupby dwukrotnie wraz z metodą rank:

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

użyć metody min argument podać wartości, które podzielają te same dane1 ten sam 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') 

I następnie zgrupuj te wyniki i dodaj pozycję w odniesieniu do danych2:

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 

To wydaje się być rodzimym sposobem na zrobienie tego (może być!).

+0

Zgadzam się, ranking według wielu kolumn wydaje się naturalny ... czy powinienem poprosić o to na githubie? – AllenQ

+0

również dziękuję za obejście problemu! – AllenQ

+0

@AllenQ już zrobił https://github.com/pydata/pandas/issues/4311 :) –

-1

pandas.lib.fast_zip() może utworzyć tablicę krotek z listy tablic. Można użyć tej funkcji, aby stworzyć serię krotka, a następnie rangi go:

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 

wynik:

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

można również użyć sort_values(), groupby() i wreszcie cumcount() + 1:

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

:

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 testowane z pandami 0.18

+0

Po prostu wypróbowałem tę metodę i otrzymałem następujące ostrzeżenie: SettingWithCopyWarning: Wartość próbuje ustawić na kopii plasterka z a DataFrame. Spróbuj użyć .loc [row_indexer, col_indexer] = wartość zamiast –

+0

@AndrewL, twoje 'df' wydaje się być" kopią kopii plasterka DF "... wykonuje następujące polecenie dla twojego' df': ' df ['new'] = 0' __ bez ostrzeżenia? – MaxU

+0

dzięki za twój wgląd. Powyższe polecenie faktycznie działa bez rzucania ostrzeżenia. Czy możesz wyjaśnić, w jaki sposób ta "kopia plasterka DF" jest problematyczna i dlaczego jest obsługiwana inaczej niż zwykły df? Dziękuję Ci! –

0

Można użyć transform i Rank razem Oto przykład

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

Zapraszamy do obejrzenia Pandy Rank metody aby uzyskać więcej informacji