Appliquer la fonction pandas à la colonne pour créer plusieurs nouvelles colonnes?
Comment faire cela dans les pandas:
j'ai une fonction extract_text_features
sur une seule colonne de texte, retournant plusieurs colonnes de sortie. Plus précisément, la fonction renvoie 6 valeurs.
la fonction fonctionne, cependant il ne semble pas y avoir de type de retour approprié (pandas DataFrame/ numpy array / Python list) tel que la sortie puisse être correctement assignée df.ix[: ,10:16] = df.textcol.map(extract_text_features)
donc je pense que je dois revenir à iterating avec df.iterrows()
, comme dans ce ?
mise à jour:
Itérer avec df.iterrows()
est au moins 20x plus lent, donc j'ai abandonné et divisé la fonction en six appels distincts .map(lambda ...)
.
10 réponses
construire à partir de la réponse de l'utilisateur 1827356 ,vous pouvez faire la tâche en un seul passage en utilisant df.merge
:
df.merge(df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1})),
left_index=True, right_index=True)
textcol feature1 feature2
0 0.772692 1.772692 -0.227308
1 0.857210 1.857210 -0.142790
2 0.065639 1.065639 -0.934361
3 0.819160 1.819160 -0.180840
4 0.088212 1.088212 -0.911788
je le fais habituellement en utilisant zip
:
>>> df = pd.DataFrame([[i] for i in range(10)], columns=['num'])
>>> df
num
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
>>> def powers(x):
>>> return x, x**2, x**3, x**4, x**5, x**6
>>> df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \
>>> zip(*df['num'].map(powers))
>>> df
num p1 p2 p3 p4 p5 p6
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
2 2 2 4 8 16 32 64
3 3 3 9 27 81 243 729
4 4 4 16 64 256 1024 4096
5 5 5 25 125 625 3125 15625
6 6 6 36 216 1296 7776 46656
7 7 7 49 343 2401 16807 117649
8 8 8 64 512 4096 32768 262144
9 9 9 81 729 6561 59049 531441
C'est ce que j'ai fait dans le passé
df = pd.DataFrame({'textcol' : np.random.rand(5)})
df
textcol
0 0.626524
1 0.119967
2 0.803650
3 0.100880
4 0.017859
df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))
feature1 feature2
0 1.626524 -0.373476
1 1.119967 -0.880033
2 1.803650 -0.196350
3 1.100880 -0.899120
4 1.017859 -0.982141
édition complète
pd.concat([df, df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))], axis=1)
textcol feature1 feature2
0 0.626524 1.626524 -0.373476
1 0.119967 1.119967 -0.880033
2 0.803650 1.803650 -0.196350
3 0.100880 1.100880 -0.899120
4 0.017859 1.017859 -0.982141
C'est la façon correcte et la plus facile d'accomplir ceci pour 95% des cas d'utilisation:
>>> df = pd.DataFrame(zip(*[range(10)]), columns=['num'])
>>> df
num
0 0
1 1
2 2
3 3
4 4
5 5
>>> def example(x):
... x['p1'] = x['num']**2
... x['p2'] = x['num']**3
... x['p3'] = x['num']**4
... return x
>>> df = df.apply(example, axis=1)
>>> df
num p1 p2 p3
0 0 0 0 0
1 1 1 1 1
2 2 4 8 16
3 3 9 27 81
4 4 16 64 256
résumé: si vous ne voulez créer que quelques colonnes, utilisez df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)
Pour cette solution, le nombre de colonnes que vous créez doit être égal au nombre de colonnes à utiliser comme entrée pour l' .appliquer() fonction. Si vous voulez faire autre chose, regardez les autres réponses.
détails Disons que vous avez une base de données à deux colonnes. La première colonne est la taille d'une personne quand ils sont 10; le second est dit de la personne de la hauteur quand ils sont 20.
supposons que vous devez calculer à la fois la moyenne des hauteurs de chaque personne et la somme des hauteurs de chaque personne. C'est deux valeurs pour chaque ligne.
vous pouvez le faire via la fonction suivante, qui sera bientôt appliquée:
def mean_and_sum(x):
"""
Calculates the mean and sum of two heights.
Parameters:
:x -- the values in the row this function is applied to. Could also work on a list or a tuple.
"""
sum=x[0]+x[1]
mean=sum/2
return [mean,sum]
, Vous pouvez utiliser cette fonction comme suit:
df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)
(pour être clair: cette fonction d'application prend les valeurs de chaque ligne dans le incorporée dans un jeu partiel dataframe et renvoie une liste.)
cependant, si vous faites ceci:
df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)
vous allez créer une nouvelle colonne qui contient les listes [mean,sum], que vous voudriez probablement éviter, parce que cela nécessiterait un autre Lambda/Apply.
à la place, vous voulez décomposer chaque valeur dans sa propre colonne. Pour ce faire, vous pouvez créer deux colonnes à la fois:
df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']]
.apply(mean_and_sum(x),axis=1)
j'ai regardé plusieurs façons de faire ceci et la méthode montrée ici (retourner une série de pandas) ne semble pas être la plus efficace.
si nous commençons par une grande base de données de données aléatoires:
# Setup a dataframe of random numbers and create a
df = pd.DataFrame(np.random.randn(10000,3),columns=list('ABC'))
df['D'] = df.apply(lambda r: ':'.join(map(str, (r.A, r.B, r.C))), axis=1)
columns = 'new_a', 'new_b', 'new_c'
l'exemple ci - dessous:
# Create the dataframe by returning a series
def method_b(v):
return pd.Series({k: v for k, v in zip(columns, v.split(':'))})
%timeit -n10 -r3 df.D.apply(method_b)
10 boucles, le meilleur de 3: 2.77 s par boucle
une autre méthode:
# Create a dataframe from a series of tuples
def method_a(v):
return v.split(':')
%timeit -n10 -r3 pd.DataFrame(df.D.apply(method_a).tolist(), columns=columns)
10 boucles, le meilleur de 3: 8,85 ms par boucle
à mon avis, il est beaucoup plus efficace de prendre une série de tuples et de la convertir en une base de données. Je serais intéressé d'entendre les gens penser si il ya une erreur dans mon travail.
la solution acceptée va être extrêmement lente pour beaucoup de données. La solution avec le plus grand nombre d'upvotes est un peu difficile à lire et aussi lente avec les données numériques. Si chaque nouvelle colonne peut être calculée indépendamment des autres, je les assignerais directement sans utiliser apply
.
exemple avec de fausses données de caractère
créer 100 000 chaînes dans une DataFrame
df = pd.DataFrame(np.random.choice(['he jumped', 'she ran', 'they hiked'],
size=100000, replace=True),
columns=['words'])
df.head()
words
0 she ran
1 she ran
2 they hiked
3 they hiked
4 they hiked
disons que nous voulions extraire quelques traits de texte comme fait dans la question originale. Par exemple, extrayons le premier caractère, comptons l'occurrence de la lettre 'e' et capitalisons la phrase.
df['first'] = df['words'].str[0]
df['count_e'] = df['words'].str.count('e')
df['cap'] = df['words'].str.capitalize()
df.head()
words first count_e cap
0 she ran s 1 She ran
1 she ran s 1 She ran
2 they hiked t 2 They hiked
3 they hiked t 2 They hiked
4 they hiked t 2 They hiked
Timings
%%timeit
df['first'] = df['words'].str[0]
df['count_e'] = df['words'].str.count('e')
df['cap'] = df['words'].str.capitalize()
127 ms ± 585 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
def extract_text_features(x):
return x[0], x.count('e'), x.capitalize()
%timeit df['first'], df['count_e'], df['cap'] = zip(*df['words'].apply(extract_text_features))
101 ms ± 2.96 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
étonnamment, vous pouvez obtenir de meilleures performances en boucle à travers chaque valeur
%%timeit
a,b,c = [], [], []
for s in df['words']:
a.append(s[0]), b.append(s.count('e')), c.append(s.capitalize())
df['first'] = a
df['count_e'] = b
df['cap'] = c
79.1 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
un autre exemple avec de fausses données numériques
créer 1 million de nombres aléatoires et tester la fonction powers
d'en haut.
df = pd.DataFrame(np.random.rand(1000000), columns=['num'])
def powers(x):
return x, x**2, x**3, x**4, x**5, x**6
%%timeit
df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \
zip(*df['num'].map(powers))
1.35 s ± 83.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
attribuer chaque colonne est 25x plus rapide et très lisible:
%%timeit
df['p1'] = df['num'] ** 1
df['p2'] = df['num'] ** 2
df['p3'] = df['num'] ** 3
df['p4'] = df['num'] ** 4
df['p5'] = df['num'] ** 5
df['p6'] = df['num'] ** 6
51.6 ms ± 1.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
j'ai fait une réponse similaire avec plus de détails ici sur pourquoi apply
n'est généralement pas la voie à suivre.
En 2018, j'utilise apply()
avec l'argument result_type='expand'
>>> appiled_df = df.apply(lambda row: fn(row.text), axis='columns', result_type='expand')
>>> df = pd.concat([df, appiled_df], axis='columns')
vous pouvez retourner la ligne entière au lieu des valeurs:
df = df.apply(extract_text_features,axis = 1)
où la fonction renvoie la ligne
def extract_text_features(row):
row['new_col1'] = value1
row['new_col2'] = value2
return row
ont affiché la même réponse dans deux autres questions similaires. La façon dont je préfère le faire est d'envelopper les valeurs de retour de la fonction dans une série:
def f(x):
return pd.Series([x**2, x**3])
et utiliser ensuite appliquer comme suit pour créer des colonnes séparées:
df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)