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, renvoyant plusieurs colonnes de sortie. Plus précisément, la fonction renvoie 6 valeurs.

La fonction fonctionne, mais il ne semble pas y avoir de type de retour approprié (pandas DataFrame / tableau numpy / liste Python) de sorte 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 à l’itération avec df.iterrows() , selon cela ?

UPDATE: df.iterrows() avec df.iterrows() est au moins 20 fois plus lent, donc je me suis rendu et j’ai divisé la fonction en six .map(lambda ...) distincts .map(lambda ...) .

    En vous basant sur la réponse de user1827356, vous pouvez effectuer l’affectation en une seule fois 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 

    Éditer pour être complet

     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 manière la plus simple et la plus efficace d’y parvenir 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 voulez seulement créer 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 nouvelles colonnes que vous créez doit être égal au nombre de colonnes que vous utilisez en entrée de la fonction .apply (). Si vous voulez faire autre chose, regardez les autres réponses.

    Détails Supposons que vous ayez un dataframe à deux colonnes. La première colonne est la taille d’une personne quand elle a 10 ans; la seconde est la taille de la personne lorsqu’elle a 20 ans.

    Supposons que vous ayez besoin de calculer à la fois la moyenne des hauteurs de chaque personne et la sum des hauteurs de chaque personne. C’est deux valeurs par 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 pourriez utiliser cette fonction comme ceci:

      df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1) 

    (Pour être clair: cette fonction applique les valeurs de chaque ligne dans le fichier de données subsetté et retourne 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 1 nouvelle colonne contenant les listes [moyenne, sum], que vous voudrez probablement éviter, car cela nécessiterait un autre Lambda / Apply.

    Au lieu de cela, vous souhaitez séparer 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 examiné plusieurs façons de faire cela et la méthode présentée ici (retour d’une série de pandas) ne semble pas être la plus efficace.

    Si on commence avec un dataframe volumineux 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, (rA, rB, rC))), axis=1) columns = 'new_a', 'new_b', 'new_c' 

    L’exemple montré ici:

     # 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, meilleur de 3: 2,77 s par boucle

    Une méthode alternative:

     # 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, meilleur de 3: 8,85 ms par boucle

    À mon avis, il est beaucoup plus efficace de prendre une série de tuples, puis de les convertir en un DataFrame. Je serais intéressé d’entendre la reflection des gens s’il y a une erreur dans mon travail.

    La solution acceptée sera extrêmement lente pour beaucoup de données. La solution avec le plus grand nombre de notes d’achat est un peu difficile à lire et elle est également lente avec les données numériques. Si chaque nouvelle colonne peut être calculée indépendamment des autres, je voudrais simplement atsortingbuer directement chacune d’elles sans utiliser apply .

    Exemple avec des données de faux caractères

    Créer 100 000 chaînes dans un 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 certaines fonctionnalités du texte comme dans la question originale. Par exemple, extrayons le premier caractère, comptons l’occurrence de la lettre “e” et mettons en majuscule 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 

    Les temps

     %%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 parcourant 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éez 1 million de nombres aléatoires et testez la fonction de powers ci-dessus.

     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) 

    L’atsortingbution de chaque colonne est 25 fois 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 pour savoir pourquoi apply n’est généralement pas la voie à suivre.

    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