2017-10-05 4 views
2

Je parcours une base de données Python et je la trouve extrêmement lente. Je comprends que dans Pandas vous essayez de tout vectoriser, mais dans ce cas j'ai spécifiquement besoin d'itérer (ou s'il est possible de vectoriser, je ne sais pas comment le faire).Comment itérer sur une trame de données pandas en faisant référence aux lignes précédentes?

La logique est simple: vous avez deux colonnes "A" et "B" et une colonne de résultat "signal". Si A est égal à 1, alors vous réglez le signal sur 1. Si B est égal à 1, alors vous réglez le signal sur 0. Sinon, les signaux sont ce qu'ils étaient auparavant. En d'autres termes, la colonne A est un signal "on", la colonne B est un signal "off", et "signal" représente l'état.

Voici mon code:

def signals(indata): 
    numrows = len(indata) 
    data = pd.DataFrame(index= range(0,numrows)) 
    data['A'] = indata['A'] 
    data['B'] = indata['B'] 
    data['signal'] = 0 


    for i in range(1,numrows): 
     if data['A'].iloc[i] == 1: 
      data['signal'].iloc[i] = 1 
     elif data['B'].iloc[i] == 1: 
      data['signal'].iloc[i] = 0 
     else: 
      data['signal'].iloc[i] = data['signal'].iloc[i-1] 
    return data 

entrée/sortie Exemple:

indata = pd.DataFrame(index = range(0,10)) 
indata['A'] = [0, 1, 0, 0, 0, 0, 1, 0, 0, 0] 
indata['B'] = [1, 0, 0, 0, 1, 0, 0, 0, 1, 1] 

signals(indata) 

Sortie:

A B signal 
0 0 1 0 
1 1 0 1 
2 0 0 1 
3 0 0 1 
4 0 1 0 
5 0 0 0 
6 1 0 1 
7 0 0 1 
8 0 1 0 
9 0 1 0 

Cette logique simple prend mon ordinateur 46 secondes pour exécuter sur un dataframe de 2000 lignes avec des données générées aléatoirement.

Répondre

0

La réponse la plus simple à mon problème était de ne pas écrire à la trame de données en itérer à travers elle. J'ai créé un tableau de zéros en numpy, puis j'ai fait ma logique itérative dans le tableau. Puis j'ai écrit le tableau dans la colonne de mon dataframe.

def signals3(indata): 
    numrows = len(indata) 
    data = pd.DataFrame(index= range(0,numrows)) 

    data['A'] = indata['A'] 
    data['B'] = indata['B'] 
    out_signal = np.zeros(numrows) 

    for i in range(1,numrows): 
     if data['A'].iloc[i] == 1: 
      out_signal[i] = 1 
     elif data['B'].iloc[i] == 1: 
      out_signal[i] = 0 
     else: 
      out_signal[i] = out_signal[i-1] 


    data['signal'] = out_signal 

    return data 

Sur une trame de données de 2000 lignes de données aléatoires, ce ne prend que 43 millisecondes par opposition à 46 secondes (~ 1000 X plus rapide).

J'ai également essayé une variante où j'ai affecté les colonnes de données A et B à la série, puis itéré à travers la série. C'était un peu plus rapide (27 millisecondes). Mais il semble que la plus grande partie de la lenteur soit écrite dans une base de données. Les réponses de coldspeed et de djk ont ​​été plus rapides que celles de ma solution (environ 4,5 ms) mais en pratique, je vais probablement passer en revue les séries même si ce n'est pas optimal.

2
df['signal'] = df.A.groupby((df.A != df.B).cumsum()).transform('head', 1) 

df 
    A B signal 
0 0 1  0 
1 1 0  1 
2 0 0  1 
3 0 0  1 
4 0 1  0 
5 0 0  0 
6 1 0  1 
7 0 0  1 
8 0 1  0 
9 0 1  0 

La logique consiste ici à diviser votre série en groupes basés sur l'inégalité entre les A et B, et la valeur de chaque groupe est déterminé par A.

+0

Il est intéressant d'observer comment les pandas ont géré cette opération de porte logique, je suis encore sous le choc que cela fonctionne. –

+0

Ceci est vraiment propre, pouvez-vous expliquer un peu plus comment ça marche? – DJK

+0

Ceci est une solution intéressante, mais je ne serais pas capable de trouver cette logique par moi-même pour des itérations plus compliquées. Je suis vraiment à la recherche d'un moyen simple, facilement réplicable pour réduire le temps de traitement pour ce type de problème. – karakumy

1

Vous ne devez itérer à tout ce que vous pouvez faire une indexation booléenne

#set condition for A 
indata.loc[indata.A == 1,'signal'] = 1 
#set condition for B 
indata.loc[indata.B == 1,'signal'] = 0 
#forward fill NaN values 
indata.signal.fillna(method='ffill',inplace=True)