J'ai un dataframe qui ressemble à ceci:
index value previous_smaller_index previous_1_index 0 1 -1 -1 1 1 -1 -1 2 2 1 1 3 3 2 1 4 2 1 1 5 1 -1 -1 6 1 -1 -1
ce que je veux, c'est que chaque valeur renvoie l'index de la plus petite valeur précédente, et, en plus, l'index de la précédente Valeur "1". Si la valeur est 1, je n'en ai pas besoin (les deux valeurs peuvent être -1 ou quelque chose).
Donc, ce que je cherche, c'est:
index value 0 1 1 1 2 2 3 3 4 2 5 1 6 1
J'ai essayé d'utiliser des fonctions de roulement, cumulatives, etc. mais je ne pouvais pas le comprendre. Toute aide serait appréciée!
Modifier: SpghttCd a déjà fourni une solution intéressante pour le problème "précédent 1". Je recherche une jolie doublure pandas pour le "petit" problème précédent. (même si, bien sûr, des solutions plus agréables et plus efficaces sont les bienvenues pour les deux problèmes)
4 Réponses :
Cette fonction devrait fonctionner:
value previous_small previous_1 0 1 -1 -1 1 1 -1 -1 2 2 1 1 3 3 2 1 4 2 1 1 5 1 -1 -1 6 1 -1 -1
Sortie:
def func(values, null_val=-1):
# Initialize with arbitrary value
prev_small = values * -2
prev_1 = values * -2
# Loop through values and find previous values
for n, x in enumerate(values):
prev_vals = values.iloc[:n]
prev_small[n] = prev_vals[prev_vals < x].index[-1] if (prev_vals < x).any() else null_val
prev_1[n] = prev_vals[prev_vals == 1].index[-1] if x != 1 and (prev_vals == 1).any() else null_val
return prev_small, prev_1
df = pd.DataFrame({'value': [1, 1, 2, 3, 2, 1, 1,]})
df['previous_small'], df['previous_1'] = func(df['value'])
Vous pouvez essayer
df = pd.DataFrame({'value': [1, 1, 2, 3, 2, 1, 1, 2, 3, 4, 5]})
df['prev_smaller_idx'] = df.apply(lambda x: df.index[:x.name][(x.value>df.value)[:x.name]].max(), axis=1)
df['prev_1_idx'] = pd.Series(df.index.where(df.value==1)).shift()[df.value!=1].ffill()
# value prev_smaller_idx prev_1_idx
#0 1 NaN NaN
#1 1 NaN NaN
#2 2 1.0 1.0
#3 3 2.0 1.0
#4 2 1.0 1.0
#5 1 NaN NaN
#6 1 NaN NaN
#7 2 6.0 6.0
#8 3 7.0 6.0
#9 4 8.0 6.0
#10 5 9.0 6.0
"previous_smaller_index" peut être trouvé en utilisant une comparaison diffusée numpy vectorisée avec argmax.
"previous_1_index" peut être résolu en utilisant groupby et idxmax sur un masque médical cumsum .
m = df.value.eq(1)
df['previous_smaller_index'] = np.where(
m, -1, len(df) - np.triu(df.value.values < df.value[:,None]).argmax(1) - 1
)[::-1]
# Optimizing @SpghttCd's `previous_1_index` calculation a bit
df['previous_1_index'] = (np.where(
m, -1, df.index.where(m).to_series(index=df.index).ffill(downcast='infer'))
)
df
index value previous_1_index previous_smaller_index
0 0 1 -1 -1
1 1 1 -1 -1
2 2 2 1 1
3 3 3 1 2
4 4 2 1 1
5 5 1 -1 -1
6 6 1 -1 -1
df index value previous_smaller_index previous_1_index 0 0 1 -1 -1 1 1 1 -1 -1 2 2 2 1 1 3 3 3 2 1 4 4 2 1 1 5 5 1 -1 -1 6 6 1 -1 -1
Si vous les voulez comme une seule doublure, vous pouvez froncer quelques lignes ensemble en un seul:
m = df.value.eq(1)
u = np.triu(df.value.values < df.value[:,None]).argmax(1)
v = m.cumsum()
df['previous_smaller_index'] = np.where(m, -1, len(df) - u - 1)
df['previous_1_index'] = v.groupby(v).transform('idxmax').mask(m, -1)
Performance globale
La configuration et l'analyse comparative des performances ont été effectuées à l'aide de perfplot . Le code peut être trouvé à cet essentiel .
Les temps sont relatifs (l'échelle des y est logarithmique).
previous_1_index Performance
C'est une bonne solution, et j'ai voté pour et accepté, mais j'espérais une doublure relativement courte avec des fonctions pures pandas. Alors j'attendrai avec la prime.
@BinyaminEven Je voulais juste souligner que j'ai ajouté des versions 1-liner pour mes solutions.
Je vous remercie! c'est une solution intéressante, j'ai besoin de la décomposer pour bien la comprendre. il sera intéressant de comparer l'efficacité avec la solution de SpghttCd. son code, en particulier pour prev_1, est beaucoup plus court.
@BinyaminEven Gardez à l'esprit que la brièveté! = La performance. Consultez cette réponse où une fonction numpy de 25 lignes surpasse les pandas one liner.
Je n'ai pas dit que son code était plus efficace, en fait pour prev_small il utilise apply et vous ne le faites pas. Mais j'ai le sentiment que son code pour prev_1 sera plus rapide. il doit être "timeit-ed".
c'est génial! Je ne connaissais pas perfplot ... pouvez-vous comparer uniquement les performances de prev_1? Il est assez clair que votre solution dans son ensemble est plus efficace, mais il sera intéressant de voir une comparaison pour prev_1.
@BinyaminEven j'ai ajouté des horaires avec une solution mise à jour.
OK merci! parce que je lui ai promis, j'accepterai sa réponse, mais je vous donnerai volontiers la prime (je peux la donner dans 8 heures environ).
@ W-B Bien sûr, donnez-moi un peu de temps. Je l'aurai bientôt.
Voici pour faire le previous_smaller_index
df['previous_1_index']=df['index'].where(df.value==1).ffill().where(df.value!=1,-1) df Out[79]: index value previous_smaller_index previous_1_index 0 0 1 -1 -1.0 1 1 1 -1 -1.0 2 2 2 1 1.0 3 3 3 2 1.0 4 4 2 1 1.0 5 5 1 -1 -1.0 6 6 1 -1 -1.0
Obtenir le précédent 1
df['index'].where(df.value==1).ffill().where(df.value!=1,-1) Out[77]: 0 -1.0 1 -1.0 2 1.0 3 1.0 4 1.0 5 -1.0 6 -1.0 Name: index, dtype: float64
Réattribuez-le
l=list(zip(df['index'],df.value))[::-1]
t=[]
n=len(l)
for x in l:
if x[1]==1:
t.append(-1)
else:
t.append(next(y for y in l[n-x[0]:] if y[1]<x[1])[0])
df['previous_smaller_index']=t[::-1]
df
Out[71]:
index value previous_smaller_index
0 0 1 -1
1 1 1 -1
2 2 2 1
3 3 3 2
4 4 2 1
5 5 1 -1
6 6 1 -1
Qu'est-ce que l ici? Pouvez-vous réécrire la première partie pour qu'elle soit dans le contexte de df ?
De plus, lorsque j'exécute votre code pour 'previous_smaller_index', j'obtiens t = [-1, -1, 5.0, 4.0, 5.0, -1, -1] pouvez-vous vérifier?
@coldspeed l'a compris, j'ai oublié de copier le code complet ici, éditez! :-) désolé pour cet homme
@coldspeed ne semble pas encore assez rapide, les performances de vos solutions sont bien meilleures
C'est une belle alternative. Je pense que l'optimisation de previous_1_index sera plus difficile sans Numba. Et étant donné qu'OP veut "one liners", je ne sais pas à quel point ils seraient intéressés par une solution numba.
@coldspeed Numba devrait être encore plus rapide. une ligne est chouette, mais si l'on considère les performances, je pense que la dose à plusieurs lignes signifie `` mauvais '' :-) (Juste mes 2 cents.)