web-dev-qa-db-fra.com

Appliquez une fonction à chaque ligne du cadre de données du pandas pour créer deux nouvelles colonnes.

J'ai un pandas DataFrame, st contenant plusieurs colonnes:

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 53732 entries, 1993-01-07 12:23:58 to 2012-12-02 20:06:23
Data columns:
Date(dd-mm-yy)_Time(hh-mm-ss)       53732  non-null values
Julian_Day                          53732  non-null values
AOT_1020                            53716  non-null values
AOT_870                             53732  non-null values
AOT_675                             53188  non-null values
AOT_500                             51687  non-null values
AOT_440                             53727  non-null values
AOT_380                             51864  non-null values
AOT_340                             52852  non-null values
Water(cm)                           51687  non-null values
%TripletVar_1020                    53710  non-null values
%TripletVar_870                     53726  non-null values
%TripletVar_675                     53182  non-null values
%TripletVar_500                     51683  non-null values
%TripletVar_440                     53721  non-null values
%TripletVar_380                     51860  non-null values
%TripletVar_340                     52846  non-null values
440-870Angstrom                     53732  non-null values
380-500Angstrom                     52253  non-null values
440-675Angstrom                     53732  non-null values
500-870Angstrom                     53732  non-null values
340-440Angstrom                     53277  non-null values
Last_Processing_Date(dd/mm/yyyy)    53732  non-null values
Solar_Zenith_Angle                  53732  non-null values
dtypes: datetime64[ns](1), float64(22), object(1)

Je souhaite créer deux nouvelles colonnes pour ce cadre de données en appliquant une fonction à chaque ligne du cadre de données. Je ne veux pas avoir à appeler la fonction plusieurs fois (par exemple, en effectuant deux appels apply distincts) car elle nécessite beaucoup de calcul. J'ai essayé de le faire de deux manières, et aucune d'entre elles ne fonctionne:


Utilisation de apply:

J'ai écrit une fonction qui prend une Series et retourne un tuple des valeurs que je veux:

def calculate(s):
    a = s['path'] + 2*s['row'] # Simple calc for example
    b = s['path'] * 0.153
    return (a, b)

Essayer d'appliquer ceci au DataFrame donne une erreur:

st.apply(calculate, axis=1)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-248-acb7a44054a7> in <module>()
----> 1 st.apply(calculate, axis=1)

C:\Python27\lib\site-packages\pandas\core\frame.pyc in apply(self, func, axis, broadcast, raw, args, **kwds)
   4191                     return self._apply_raw(f, axis)
   4192                 else:
-> 4193                     return self._apply_standard(f, axis)
   4194             else:
   4195                 return self._apply_broadcast(f, axis)

C:\Python27\lib\site-packages\pandas\core\frame.pyc in _apply_standard(self, func, axis, ignore_failures)
   4274                 index = None
   4275 
-> 4276             result = self._constructor(data=results, index=index)
   4277             result.rename(columns=dict(Zip(range(len(res_index)), res_index)),
   4278                           inplace=True)

C:\Python27\lib\site-packages\pandas\core\frame.pyc in __init__(self, data, index, columns, dtype, copy)
    390             mgr = self._init_mgr(data, index, columns, dtype=dtype, copy=copy)
    391         Elif isinstance(data, dict):
--> 392             mgr = self._init_dict(data, index, columns, dtype=dtype)
    393         Elif isinstance(data, ma.MaskedArray):
    394             mask = ma.getmaskarray(data)

C:\Python27\lib\site-packages\pandas\core\frame.pyc in _init_dict(self, data, index, columns, dtype)
    521 
    522         return _arrays_to_mgr(arrays, data_names, index, columns,
--> 523                               dtype=dtype)
    524 
    525     def _init_ndarray(self, values, index, columns, dtype=None,

C:\Python27\lib\site-packages\pandas\core\frame.pyc in _arrays_to_mgr(arrays, arr_names, index, columns, dtype)
   5411 
   5412     # consolidate for now
-> 5413     mgr = BlockManager(blocks, axes)
   5414     return mgr.consolidate()
   5415 

C:\Python27\lib\site-packages\pandas\core\internals.pyc in __init__(self, blocks, axes, do_integrity_check)
    802 
    803         if do_integrity_check:
--> 804             self._verify_integrity()
    805 
    806         self._consolidate_check()

C:\Python27\lib\site-packages\pandas\core\internals.pyc in _verify_integrity(self)
    892                                      "items")
    893             if block.values.shape[1:] != mgr_shape[1:]:
--> 894                 raise AssertionError('Block shape incompatible with manager')
    895         tot_items = sum(len(x.items) for x in self.blocks)
    896         if len(self.items) != tot_items:

AssertionError: Block shape incompatible with manager

J'allais ensuite affecter les valeurs renvoyées par apply à deux nouvelles colonnes à l'aide de la méthode indiquée dans cette question . Cependant, je ne peux même pas arriver à ce point! Tout cela fonctionne bien si je ne retourne qu'une valeur.


Utiliser une boucle:

J'ai d'abord créé deux nouvelles colonnes du cadre de données et les ai définies sur None:

st['a'] = None
st['b'] = None

Ensuite, vous avez parcouru tous les index et essayé de modifier les valeurs None que j'avais indiquées, mais les modifications que j'ai apportées ne semblent pas fonctionner. C'est-à-dire qu'aucune erreur n'a été générée, mais le DataFrame ne semble pas avoir été modifié.

for i in st.index:
    # do calc here
    st.ix[i]['a'] = a
    st.ix[i]['b'] = b

Je pensais que ces deux méthodes fonctionneraient, mais aucune d’elles n’a fonctionné. Alors, qu'est-ce que je fais mal ici? Et quelle est la meilleure façon, la plus "pythonique" et "pandaonique" de le faire?

39
robintw

Pour que la première approche fonctionne, essayez de renvoyer une série au lieu d'un tuple (apply renvoie une exception car il ne sait pas comment recoller les lignes car le nombre de colonnes ne correspond pas au cadre d'origine).

def calculate(s):
    a = s['path'] + 2*s['row'] # Simple calc for example
    b = s['path'] * 0.153
    return pd.Series(dict(col1=a, col2=b))

La deuxième approche devrait fonctionner si vous remplacez:

st.ix[i]['a'] = a

avec:

st.ix[i, 'a'] = a
27
Garrett

J'utilise toujours lambdas et la fonction map() intégrée pour créer de nouvelles lignes en combinant d'autres lignes:

st['a'] = map(lambda path, row: path + 2 * row, st['path'], st['row'])

Cela peut être un peu plus compliqué que nécessaire pour effectuer des combinaisons linéaires de colonnes numériques. D'autre part, j'estime qu'il est bon d'adopter une convention car elle peut être utilisée avec des combinaisons de lignes plus complexes (par exemple, l'utilisation de chaînes) ou le remplissage de données manquantes dans une colonne à l'aide des fonctions des autres colonnes.

Par exemple, disons que vous avez un tableau avec des colonnes genre, et titre, et que certains titres sont manquants. Vous pouvez les remplir avec une fonction comme suit:

title_dict = {'male': 'mr.', 'female': 'ms.'}
table['title'] = map(lambda title,
    gender: title if title != None else title_dict[gender],
    table['title'], table['gender'])
17
Russell_A

Ceci a été résolu ici: Appliquer la fonction pandas à la colonne pour créer plusieurs nouvelles colonnes?

Appliqué à votre question, cela devrait marcher:

def calculate(s):
    a = s['path'] + 2*s['row'] # Simple calc for example
    b = s['path'] * 0.153
    return pd.Series({'col1': a, 'col2': b})

df = df.merge(df.apply(calculate, axis=1), left_index=True, right_index=True)
5
SebastianNeubauer

Encore une autre solution basée sur Affectation de nouvelles colonnes dans les chaînes de méthodes :

st.assign(a = st['path'] + 2*st['row'], b = st['path'] * 0.153)

Attention assignalways renvoie une copie des données, en laissant le DataFrame d'origine intact.

0
Draško Kokić