web-dev-qa-db-fra.com

Fonctions de fenêtre de type SQL dans PANDAS: numérotation des lignes dans Python Pandas Dataframe)

Je viens d'un arrière-plan SQL et j'utilise fréquemment l'étape de traitement des données suivante:

  1. Partitionnez la table de données par un ou plusieurs champs
  2. Pour chaque partition, ajoutez un numéro d'attente à chacune de ses lignes qui classe la ligne selon un ou plusieurs autres champs, où l'analyste spécifie croissant ou décroissant

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        

Je cherche comment faire l'équivalent PANDAS à cette fonction de fenêtre 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

J'ai essayé ce qui suit que j'ai pu travailler là où il n'y a pas de "partitions":

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

J'ai essayé d'étendre cette idée pour qu'elle fonctionne avec des partitions (groupes de pandas) mais ce qui suit n'a pas fonctionné:

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)

Mais je viens de recevoir beaucoup de NaN quand je fais ça.

Idéalement, il y aurait un moyen succinct de répliquer la capacité de fonction de fenêtre de sql (j'ai compris les agrégats basés sur les fenêtres ... c'est une ligne dans les pandas) ... quelqu'un peut-il partager avec moi la manière la plus idiomatique de nombre de lignes comme celle-ci dans PANDAS?

28
AllenQ

Vous pouvez le faire en utilisant groupby deux fois avec la méthode rank:

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

Utilisez l'argument de la méthode min pour donner aux valeurs qui partagent les mêmes données1 le même 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')

Ensuite, groupez ces résultats et ajoutez le classement par rapport aux données2:

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

On dirait qu'il devrait y avoir une façon native de faire ça (il pourrait bien y en avoir! ...).

14
Andy Hayden

vous pouvez également utiliser sort_values(), groupby() et enfin cumcount() + 1:

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

rendements:

   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 testé avec pandas 0,18

33
MaxU

Vous pouvez utiliser transform et Rank ensemble Voici un exemple

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

Jetez un œil à Pandas Méthode de classement pour plus d'informations

7
sushmit

Utilisez la fonction groupby.rank. Voici l'exemple de travail.

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

0
Gokulakrishnan

pandas.lib.fast_Zip() peut créer un tableau Tuple à partir d'une liste de tableaux. Vous pouvez utiliser cette fonction pour créer une série de tuple, puis la classer:

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

le résultat:

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