J'ai un dataframe df avec des valeurs flottantes dans la colonne A . Je souhaite ajouter une autre colonne B telle que:
B [0] = A [0]
pour i> 0 ...
B [i] = if (np.isnan (A [i])) alors A [i] else Étape 3 B [i] = si (abs ((B [i-1] - A [i]) / B [i-1]) Un exemple de dataframe df peut être généré comme indiqué ci-dessous
import numpy as np
import pandas as pd
df = pd.DataFrame(1000*(2+np.random.randn(500, 1)), columns=list('A'))
df.loc[1, 'A'] = np.nan
df.loc[15, 'A'] = np.nan
df.loc[240, 'A'] = np.nan
df.loc[241, 'A'] = np.nan
4 Réponses :
Cela peut être fait assez efficacement avec Numba . Si vous ne parvenez pas à utiliser Numba, omettez simplement @njit et votre logique fonctionnera comme une boucle de niveau Python.
import numpy as np
import pandas as pd
from numba import njit
np.random.seed(0)
df = pd.DataFrame(1000*(2+np.random.randn(500, 1)), columns=['A'])
df.loc[1, 'A'] = np.nan
df.loc[15, 'A'] = np.nan
df.loc[240, 'A'] = np.nan
@njit
def recurse_nb(x):
out = x.copy()
for i in range(1, x.shape[0]):
if not np.isnan(x[i]) and (abs(1 - x[i] / out[i-1]) < 0.3):
out[i] = out[i-1]
return out
df['B'] = recurse_nb(df['A'].values)
print(df.head(10))
A B
0 3764.052346 3764.052346
1 NaN NaN
2 2978.737984 2978.737984
3 4240.893199 4240.893199
4 3867.557990 4240.893199
5 1022.722120 1022.722120
6 2950.088418 2950.088418
7 1848.642792 1848.642792
8 1896.781148 1848.642792
9 2410.598502 2410.598502
@PaulH, peut-être, publiez une solution! Je serai intéressant de voir la différence de performance!
@jpp existe-t-il un moyen non- Numba car Numba n'est pas disponible et ne peut pas être rendu disponible dans mon environnement actuel.
Je pense que si abs ((1 - x [i]) / out [i-1]) <0.3 doit être si abs ((out [i-1] - x [i ]) / out [i-1])) <0.3 pour correspondre à la logique de la question.
@ CJ59, Bon point, corrigé maintenant, vous pouvez utiliser 1 - x [i] / out [i-1] .
Une solution simple que je pourrais trouver est la suivante. Je me demandais s'il y avait une manière plus pythonique de faire les choses:
a = df['A'].values
b = []
b.append(t[0])
for i in range(1, len(a)):
if np.isnan(a[i]):
b.append(a[i])
else:
b.append(b[i-1] if abs(1 - a[i]/b[i-1]) < 0.3 else a[i])
df['B'] = b
Cela peut donc être plus rapide pour les données du monde réel, mais il y a aussi un très mauvais scénario du pire des cas (si la ligne 0 >> le reste des données, alors la boucle while sera itérée N fois).
df['B'] = df['A']
to_be_fixed = pd.Series(True, index=df.index)
while to_be_fixed.any():
# Shift column B and the rows that need to be logically tested
diff = df['B'].shift(1)
to_be_fixed = to_be_fixed.shift(1)
# Test the rows to see which need to be replaced
to_be_fixed = to_be_fixed & (np.abs(1 - df['A'] / diff) < 0.3)
# Replace data
df.loc[to_be_fixed, 'B'] = diff.loc[to_be_fixed]
# Fix np.nan that has been introduced into column B
b_na = pd.isnull(df['B'])
df.loc[b_na, 'B'] = df.loc[b_na, 'A']
Je ne sais pas ce que vous voulez faire avec le premier B-1 et la division par NaN situation:
b = [a if np.isnan(a) or abs(b-a)/b >= 0.3 else b for a,b in zip(df.A,b1)]
selon @jpp, vous pouvez également faire une version de compréhension de liste pour la liste b:
df = pd.DataFrame([1,2,3,4,5,None,6,7,8,9,10], columns=['A'])
b1 = df.A.shift(1)
b1[0] = 1
b = list(map(lambda a,b1: a if np.isnan(a) else (b1 if abs(b1-a)/b1 < 0.3 else a), df.A, b1 ))
df['B'] = b
df
A B
0 1.0 1.0
1 2.0 2.0
2 3.0 3.0
3 4.0 4.0
4 5.0 4.0
5 NaN NaN
6 6.0 6.0
7 7.0 6.0
8 8.0 7.0
9 9.0 8.0
10 10.0 9.0
Je recommanderais une compréhension de liste plutôt que map (probablement avec une fonction personnalisée plutôt que lambda ).
Votre exemple de dataframe n'a aucun NaN. Il est probablement préférable de simplement coder en dur une trame de données avec ~ 10 lignes de données
Il n'y a probablement aucun moyen de faire cela sans boucler; aussi le comportement est comme décrit est indéterminé pour la ligne 0.
a répondu à la fois aux commentaires
Si
B [i-1]estnp.nan, comment déterminez-vous quoi mettre dansB [i]? Parce que si la déclaration est indéterminée alors.