2017-09-19 5 views
2

Je cherche un moyen de prendre une série pandas et de retourner une nouvelle série représentant le nombre de valeurs antérieures, consécutives, qui sont supérieures/inférieures à chaque rangée de la série :pandas - Nombre de valeurs plus élevé/plus bas que les rangées actuelles

a = pd.Series([30, 10, 20, 25, 35, 15]) 

... devrait afficher:

Value Higher than streak Lower than streak 
30  0     0 
10  0     1 
20  1     0 
25  2     0 
35  4     0 
15  0     3 

Cela permettra à quelqu'un d'identifier l'importance de chaque valeur "max/min régionale" est dans une série chronologique.

Merci d'avance.

Répondre

2

Puisque vous êtes à la recherche en arrière sur les valeurs précédentes pour voir s'il y a des valeurs consécutives, vous allez devoir interagir avec des indices en quelque sorte. Cette solution examine d'abord les valeurs antérieures à la valeur de l'index en cours pour voir si elles sont inférieures ou supérieures à la valeur, puis définit les valeurs sur False s'il y avait un False qui la suit. Cela évite également de créer des itérateurs sur le DataFrame, ce qui peut accélérer les opérations pour des ensembles de données plus volumineux.

import pandas as pd 
from operator import gt, lt 

a = pd.Series([30, 10, 20, 25, 35, 15]) 

def consecutive_run(op, ser, i): 
    """ 
    Sum the uninterrupted consecutive runs at index i in the series where the previous data 
    was true according to the operator. 
    """ 
    thresh_all = op(ser[:i], ser[i]) 
    # find any data where the operator was not passing. set the previous data to all falses 
    non_passing = thresh_all[~thresh_all] 
    start_idx = 0 
    if not non_passing.empty: 
     # if there was a failure, there was a break in the consecutive truth values, 
     # so get the final False position. Starting index will be False, but it 
     # will either be at the end of the series selection and will sum to zero 
     # or will be followed by all successive True values afterwards 
     start_idx = non_passing.index[-1] 
    # count the consecutive runs by summing from the start index onwards 
    return thresh_all[start_idx:].sum() 


res = pd.concat([a, a.index.to_series().map(lambda i: consecutive_run(gt, a, i)), 
       a.index.to_series().map(lambda i: consecutive_run(lt, a, i))], 
     axis=1) 
res.columns = ['Value', 'Higher than streak', 'Lower than streak'] 
print(res) 

Résultat:

Value Higher than streak Lower than streak 
0  30     0     0 
1  10     1     0 
2  20     0     1 
3  25     0     2 
4  35     0     4 
5  15     3     0 
+1

Merci, je ne pensais pas que nous trouverions une solution qui évite les boucles. –

+0

Mise à jour pour utiliser un algorithme de sommation légèrement plus efficace en ne saisissant que les valeurs successives, puis en additionnant. – benjwadams

0
import pandas as pd 
import numpy as np 

value = pd.Series([30, 10, 20, 25, 35, 15]) 



Lower=[(value[x]<value[:x]).sum() for x in range(len(value))] 
Higher=[(value[x]>value[:x]).sum() for x in range(len(value))] 


df=pd.DataFrame({"value":value,"Higher":Higher,"Lower":Lower}) 

print(df) 





     Lower Higher value 
0  0  0  30 
1  1  0  10 
2  1  1  20 
3  1  2  25 
4  0  4  35 
5  4  1  15 
+0

Merci pour la réponse.Malheureusement, cette solution n'a pas atteint les résultats attendus car chaque ligne ne devrait être évaluée que par rapport aux lignes précédentes. par exemple. à partir de la deuxième observation, 10 est inférieur à 30 - donc Colonne inférieure = 1, Colonne supérieure = 0. –

+0

J'ai édité ma réponse – 2Obe

+0

Peut-être que vous devez changer les noms pour Supérieur et Inférieur selon la logique que vous supposez – 2Obe

0

Edit: Mise à jour à vraiment count valeurs consécutives. Je ne pouvais pas trouver une solution de pandas réalisable, donc nous sommes de retour en boucle.

df = pd.Series(np.random.rand(10000)) 

def count_bigger_consecutives(values): 
    length = len(values) 
    result = np.zeros(length) 
    for i in range(length): 
    for j in range(i): 
     if(values[i]>values[j]): 
     result[i] += 1 
     else: 
     break 
    return result 

%timeit count_bigger_consecutives(df.values) 
1 loop, best of 3: 365 ms per loop 

Si la performance est une préoccupation pour vous, il est possible d'archiver avec numba speedup, un compilateur juste à temps pour le code python. Dans cet exemple, vous pouvez vraiment voir numba briller:

from numba import jit 
@jit(nopython=True) 
def numba_count_bigger_consecutives(values): 
    length = len(values) 
    result = np.zeros(length) 
    for i in range(length): 
    for j in range(i): 
     if(values[i]>values[j]): 
     result[i] += 1 
     else: 
     break 
    return result 

%timeit numba_count_bigger_consecutives(df.values) 
The slowest run took 543.09 times longer than the fastest. This could mean that an intermediate result is being cached. 
10000 loops, best of 3: 161 µs per loop 
+0

Merci. Très intéressant, je ne connaissais pas expand(). Cependant, ce n'est pas exactement le comportement attendu. J'ai besoin de connaître le nombre maximum d'observations passées consécutives dans ma série temporelle qui ferait toujours la ligne courante = max() ou min(). –

+0

@BrunoVieira J'ai mis à jour ma solution. –

+0

Wow. C'était beaucoup plus rapide. Merci d'avoir partagé cette solution. Malheureusement, le résultat est sorti en tant que tableau ([0., 0., 0., 0., 4., 0.]) alors que je m'attendais à 0, 0, 1, 2, 4, 0. Comme il ressemble à la solution nécessitera toujours une boucle, votre suggestion d'utiliser numba est toujours très utile. –

0

Voici la solution d'un collègue est venu avec (sans doute pas le plus efficace, mais il fait le tour):

Données d'entrée

a = pd.Series([30, 10, 20, 25, 35, 15]) 

Créer une colonne 'plus'

b = [] 

for idx, value in enumerate(a): 
    count = 0 
    for i in range(idx, 0, -1): 
     if value < a.loc[i-1]: 
      break 
     count += 1 
    b.append([value, count]) 

higher = pd.DataFrame(b, columns=['Value', 'Higher']) 

Créer une colonne 'inférieure'

c = [] 

for idx, value in enumerate(a): 
    count = 0 
    for i in range(idx, 0, -1): 
     if value > a.loc[i-1]: 
      break 
     count += 1 
    c.append([value, count]) 

lower = pd.DataFrame(c, columns=['Value', 'Lower']) 

fusionner les deux nouvelles séries

print(pd.merge(higher, lower, on='Value')) 

    Value Higher Lower 
0  30  0  0 
1  10  0  1 
2  20  1  0 
3  25  2  0 
4  35  4  0 
5  15  0  3 
1

Ceci est ma solution - il a une boucle, mais le nombre d'itérations ne sera la longueur maximale de la striure. Il stocke un état indiquant si la série pour chaque ligne a été calculée et s'arrête lorsque cela est fait. Il utilise shift pour tester si la rangée précédente est supérieure/inférieure et continue d'augmenter le décalage jusqu'à ce que toutes les traînées soient trouvées.

a = pd.Series([30, 10, 20, 25, 35, 15, 15]) 

a_not_done_greater = pd.Series(np.ones(len(a))).astype(bool) 
a_not_done_less = pd.Series(np.ones(len(a))).astype(bool) 

a_streak_greater = pd.Series(np.zeros(len(a))).astype(int) 
a_streak_less = pd.Series(np.zeros(len(a))).astype(int) 

s = 1 
not_done_greater = True 
not_done_less = True 

while not_done_greater or not_done_less: 
    if not_done_greater: 
     a_greater_than_shift = (a > a.shift(s)) 
     a_streak_greater = a_streak_greater + (a_not_done_greater.astype(int) * a_greater_than_shift) 
     a_not_done_greater = a_not_done_greater & a_greater_than_shift 
     not_done_greater = a_not_done_greater.any() 

    if not_done_less: 
     a_less_than_shift = (a < a.shift(s)) 
     a_streak_less = a_streak_less + (a_not_done_less.astype(int) * a_less_than_shift) 
     a_not_done_less = a_not_done_less & a_less_than_shift 
     not_done_less = a_not_done_less.any() 

    s = s + 1 


res = pd.concat([a, a_streak_greater, a_streak_less], axis=1) 
res.columns = ['value', 'greater_than_streak', 'less_than_streak'] 
print(res) 

Ce qui donne la trame de données

value greater_than_streak less_than_streak 
0  30     0     0 
1  10     0     1 
2  20     1     0 
3  25     2     0 
4  35     4     0 
5  15     0     3 
6  15     0     0