5
votes

remplissage conditionnel dans le cadre de données pandas

J'ai un dataframe df avec des valeurs flottantes dans la colonne A . Je souhaite ajouter une autre colonne B telle que:

  1. B [0] = A [0]

    pour i> 0 ...

  2. B [i] = if (np.isnan (A [i])) alors A [i] else Étape 3
  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 commentaires

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] est np.nan , comment déterminez-vous quoi mettre dans B [i] ? Parce que si la déclaration est indéterminée alors.


4 Réponses :


2
votes

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


4 commentaires

@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] .



1
votes

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


0 commentaires

0
votes

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


0 commentaires

3
votes

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


1 commentaires

Je recommanderais une compréhension de liste plutôt que map (probablement avec une fonction personnalisée plutôt que lambda ).