Je recherche une fonction qui permet d'atteindre les objectifs suivants. Il est préférable de le montrer dans un exemple. Considérer:

pd.DataFrame([ [1, 2, 3 ], [4, 5, np.nan ]], columns=['x', 'y1', 'y2'])

Qui ressemble à:

   x  y1   y2
0  1   2  3
1  4   5  NaN

Je voudrais réduire les colonnes y1 et y2, en allongeant le DataFrame si nécessaire, afin que la sortie soit:

   x  y
0  1   2  
1  1   3  
2  4   5  

Autrement dit, une ligne pour chaque combinaison entre x et y1, ou x et y2. Je recherche une fonction qui le fait relativement efficacement, car j'ai plusieurs y et plusieurs lignes.

2
splinter 23 mai 2018 à 09:36

3 réponses

Meilleure réponse

En voici un basé sur NumPy, car vous cherchiez des performances -

def gather_columns(df):
    col_mask = [i.startswith('y') for i in df.columns]
    ally_vals = df.iloc[:,col_mask].values
    y_valid_mask = ~np.isnan(ally_vals)

    reps = np.count_nonzero(y_valid_mask, axis=1)
    x_vals = np.repeat(df.x.values, reps)
    y_vals = ally_vals[y_valid_mask]
    return pd.DataFrame({'x':x_vals, 'y':y_vals})

Exemple d'exécution -

In [78]: df #(added more cols for variety)
Out[78]: 
   x  y1   y2   y5   y7
0  1   2  3.0  NaN  NaN
1  4   5  NaN  6.0  7.0

In [79]: gather_columns(df)
Out[79]: 
   x    y
0  1  2.0
1  1  3.0
2  4  5.0
3  4  6.0
4  4  7.0

Si les colonnes y commencent toujours à partir de la deuxième colonne jusqu'à la fin, nous pouvons simplement découper la trame de données et ainsi obtenir une amélioration supplémentaire des performances, comme ceci -

def gather_columns_v2(df):
    ally_vals = df.iloc[:,1:].values
    y_valid_mask = ~np.isnan(ally_vals)

    reps = np.count_nonzero(y_valid_mask, axis=1)
    x_vals = np.repeat(df.x.values, reps)
    y_vals = ally_vals[y_valid_mask]
    return pd.DataFrame({'x':x_vals, 'y':y_vals})
1
Divakar 23 mai 2018 à 07:14

Répétez tous les éléments de la première colonne en fonction du nombre de valeurs non nulles dans chaque ligne. Ensuite, créez simplement votre trame de données finale en utilisant le reste des valeurs non nulles dans les autres colonnes. Vous pouvez utiliser la méthode DataFrame.count() pour compter les valeurs non nulles et numpy.repeat() pour répéter un tableau basé sur un tableau de comptage respectif.

>>> rest = df.loc[:,'y1':]
>>> pd.DataFrame({'x': np.repeat(df['x'], rest.count(1)).values,
                  'y': rest.values[rest.notna()]})

Démo:

>>> df
    x   y1   y2   y3   y4
0   1  2.0  3.0  NaN  6.0
1   4  5.0  NaN  9.0  3.0
2  10  NaN  NaN  NaN  NaN
3   9  NaN  NaN  6.0  NaN
4   7  6.0  NaN  NaN  NaN

>>> rest = df.loc[:,'y1':]
>>> pd.DataFrame({'x': np.repeat(df['x'], rest.count(1)).values,
                  'y': rest.values[rest.notna()]})
   x    y
0  1  2.0
1  1  3.0
2  1  6.0
3  4  5.0
4  4  9.0
5  4  3.0
6  9  6.0
7  7  6.0
2
Kasramvd 23 mai 2018 à 07:17

Vous pouvez utiliser stack pour faire avancer les choses ie

pd.DataFrame(df.set_index('x').stack().reset_index(level=0).values,columns=['x','y'])

     x    y
0  1.0  2.0
1  1.0  3.0
2  4.0  5.0
3
Bharath 23 mai 2018 à 07:16