web-dev-qa-db-fra.com

Remplacer efficacement les valeurs d'une colonne à une autre colonne Pandas DataFrame

J'ai un DataFrame Pandas comme le suivant: 

   col1 col2 col3
1   0.2  0.3  0.3
2   0.2  0.3  0.3
3     0  0.4  0.4
4     0    0  0.3
5     0    0    0
6   0.1  0.4  0.4

Je souhaite remplacer les valeurs col1 par les valeurs de la deuxième colonne (col2) uniquement si les valeurs col1 sont égales à 0 et après (pour les valeurs nulles restantes), recommencez l'opération mais avec la troisième colonne (col3). Le résultat souhaité est le suivant:

   col1 col2 col3
1   0.2  0.3  0.3
2   0.2  0.3  0.3
3   0.4  0.4  0.4
4   0.3    0  0.3
5     0    0    0
6   0.1  0.4  0.4

Je l’ai fait en utilisant la fonction pd.replace, mais cela semble trop lent .. Je pense que cela doit être un moyen plus rapide d’accomplir cela. 

df.col1.replace(0,df.col2,inplace=True)
df.col1.replace(0,df.col3,inplace=True)

existe-t-il un moyen plus rapide de le faire?, en utilisant une autre fonction au lieu de la fonction pd.replace?

6
Pablo

Utiliser np.where est plus rapide. En utilisant un modèle similaire à celui que vous avez utilisé avec replace:

df['col1'] = np.where(df['col1'] == 0, df['col2'], df['col1'])
df['col1'] = np.where(df['col1'] == 0, df['col3'], df['col1'])

Cependant, utiliser un np.where imbriqué est légèrement plus rapide:

df['col1'] = np.where(df['col1'] == 0, 
                      np.where(df['col2'] == 0, df['col3'], df['col2']),
                      df['col1'])

Timings

Utilisation de la configuration suivante pour produire un échantillon plus volumineux de DataFrame et de fonctions de minutage:

df = pd.concat([df]*10**4, ignore_index=True)

def root_nested(df):
    df['col1'] = np.where(df['col1'] == 0, np.where(df['col2'] == 0, df['col3'], df['col2']), df['col1'])
    return df

def root_split(df):
    df['col1'] = np.where(df['col1'] == 0, df['col2'], df['col1'])
    df['col1'] = np.where(df['col1'] == 0, df['col3'], df['col1'])
    return df

def pir2(df):
    df['col1'] = df.where(df.ne(0), np.nan).bfill(axis=1).col1.fillna(0)
    return df

def pir2_2(df):
    slc = (df.values != 0).argmax(axis=1)
    return df.values[np.arange(slc.shape[0]), slc]

def andrew(df):
    df.col1[df.col1 == 0] = df.col2
    df.col1[df.col1 == 0] = df.col3
    return df

def pablo(df):
    df['col1'] = df['col1'].replace(0,df['col2'])
    df['col1'] = df['col1'].replace(0,df['col3'])
    return df

Je reçois les horaires suivants:

%timeit root_nested(df.copy())
100 loops, best of 3: 2.25 ms per loop

%timeit root_split(df.copy())
100 loops, best of 3: 2.62 ms per loop

%timeit pir2(df.copy())
100 loops, best of 3: 6.25 ms per loop

%timeit pir2_2(df.copy())
1 loop, best of 3: 2.4 ms per loop

%timeit andrew(df.copy())
100 loops, best of 3: 8.55 ms per loop

J'ai essayé de chronométrer votre méthode, mais elle fonctionne depuis plusieurs minutes sans terminer. À titre de comparaison, le chronométrage de votre méthode sur l'exemple de DataFrame à 6 lignes (et non la beaucoup plus grande testée ci-dessus) a pris 12,8 ms.

13
root

Je ne sais pas si c'est plus rapide, mais vous avez raison, vous pouvez découper le cadre de données pour obtenir le résultat souhaité.

df.col1[df.col1 == 0] = df.col2
df.col1[df.col1 == 0] = df.col3
print(df)

Sortie:

   col1  col2  col3
0   0.2   0.3   0.3
1   0.2   0.3   0.3
2   0.4   0.4   0.4
3   0.3   0.0   0.3
4   0.0   0.0   0.0
5   0.1   0.4   0.4

Alternativement, si vous voulez que ce soit plus concis (même si je ne sais pas si c'est plus rapide), vous pouvez combiner ce que vous avez fait avec ce que j'ai fait.

df.col1[df.col1 == 0] = df.col2.replace(0, df.col3)
print(df)

Sortie:

   col1  col2  col3
0   0.2   0.3   0.3
1   0.2   0.3   0.3
2   0.4   0.4   0.4
3   0.3   0.0   0.3
4   0.0   0.0   0.0
5   0.1   0.4   0.4
5
Andrew

approche en utilisant pd.DataFrame.where et pd.DataFrame.bfill

df['col1'] = df.where(df.ne(0), np.nan).bfill(axis=1).col1.fillna(0)
df

 enter image description here

Une autre approche utilisant np.argmax

def pir2(df):
    slc = (df.values != 0).argmax(axis=1)
    return df.values[np.arange(slc.shape[0]), slc]

Je sais qu’il existe un meilleur moyen d’utiliser numpy pour trancher. Je ne peux tout simplement pas y penser pour le moment.

2
piRSquared