3
votes

Obtenez l'index de la première valeur inférieure précédente

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)


0 commentaires

4 Réponses :


1
votes

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


0 commentaires

2
votes

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


0 commentaires

6
votes
  • "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 .

    li >

    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 .

    entrer l'image description here

    Les temps sont relatifs (l'échelle des y est logarithmique).


    previous_1_index Performance

    Gist avec le code pertinent.

     entrez la description de l'image ici

9 commentaires

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.



1
votes

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


6 commentaires

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