Je viens d'un arrière-plan SQL et j'utilise fréquemment l'étape de traitement des données suivante:
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?
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! ...).
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
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
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