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?
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
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'])
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)
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 assign
always renvoie une copie des données, en laissant le DataFrame d'origine intact.