Zastosuj funkcję pandy do kolumny, aby utworzyć wiele nowych kolumn?

Jak to zrobić w pandach:

Mam funkcję extract_text_features na jednej kolumnie tekstowej, zwracającą wiele kolumn wyjściowych. W szczególności funkcja zwraca 6 wartości.

Funkcja działa, jednak nie wydaje się, aby był odpowiedni typ powrotu (pandas DataFrame / numpy array/ Python list) taki, że wyjście może być poprawnie przypisane df.ix[: ,10:16] = df.textcol.map(extract_text_features)

Więc myślę, że muszę wrócić do iteracji z df.iterrows(), zgodnie z to ?

UPDATE: Iteracja z df.iterrows() jest co najmniej 20x wolniej, więc poddałem się i podzieliłem funkcję na sześć odrębnych wywołań .map(lambda ...).

Author: Community, 2013-04-26

10 answers

Bazując na odpowiedzi user1827356, możesz wykonać zadanie w jednym przejściu używając 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
 69
Author: Zelazny7,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-26 20:57:06

Zwykle robię to używając 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
 109
Author: ostrokach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-26 14:28:24

This is what I ' ve done in the past

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

Edycja dla kompletności

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
 62
Author: user1827356,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-22 17:53:40

Jest to prawidłowy i najprostszy sposób, aby to osiągnąć w 95% przypadków użycia:

>>> 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
 29
Author: Michael David Watson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-27 23:59:17

Summary: Jeśli chcesz utworzyć tylko kilka kolumn, użyj df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)

Dla tego rozwiązania, liczba nowych kolumn, które tworzysz musi być równa liczbie kolumn, których używasz jako danych wejściowych do .funkcja apply (). Jeśli chcesz zrobić coś innego, spójrz na inne odpowiedzi.

Szczegóły Powiedzmy, że masz dwukolumnową ramkę danych. Pierwsza kolumna jest wysokość osoby, gdy są one 10; drugi jest powiedział wysokość osoby, gdy są one 20.

Przypuśćmy musisz obliczyć zarówno średnią wysokości każdej osoby, jak i sumę wysokości każdej osoby. To dwie wartości w każdym wierszu.

Można to zrobić za pomocą następującej, wkrótce-być-stosowane funkcji:

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]

Możesz użyć tej funkcji w następujący sposób:

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

(dla jasności: ta funkcja apply pobiera wartości z każdego wiersza w podzestawowej ramce danych i zwraca listę.)

Jednakże, jeśli to zrobisz:

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

Utworzysz 1 nową kolumnę zawierającą [mean,sum] listy, których prawdopodobnie chciałbyś uniknąć, ponieważ wymagałoby to innego Lambda / Apply.

Zamiast tego chcesz podzielić każdą wartość na własną kolumnę. Aby to zrobić, możesz utworzyć dwie kolumny naraz:

df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']]
.apply(mean_and_sum(x),axis=1)
 12
Author: Evan W.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-08 22:34:57

Szukałem kilku sposobów na zrobienie tego i pokazana tutaj metoda (zwracanie serii pand) nie wydaje się być najbardziej skuteczna.

Jeśli zaczniemy od dużej liczby losowych danych:

# 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'

Przykład pokazany tutaj:

# 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 pętli, najlepiej 3: 2.77 s na pętlę

Metoda alternatywna:

# 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 pętli, najlepiej 3: 8.85 ms na pętlę

Moim zdaniem o wiele wydajniejsze jest robienie serii krotek i następnie przekonwertuj to na ramkę danych. Chciałbym usłyszeć, jak ludzie myślą, jeśli jest jakiś błąd w mojej pracy.

 7
Author: RFox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-06 16:44:40

Przyjęte rozwiązanie będzie bardzo powolne dla wielu danych. Rozwiązanie z największą liczbą głosów jest trochę trudne do odczytania, a także wolne od danych liczbowych. Jeśli każda nowa kolumna może być obliczona niezależnie od pozostałych, po prostu przypisałbym każdą z nich bezpośrednio bez użycia apply.

Przykład z fałszywymi danymi znaków

Utwórz 100 000 łańcuchów w ramce danych

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

Powiedzmy, że chcieliśmy wyodrębnić niektóre funkcje tekstowe, jak to zrobiono w oryginalne pytanie. Na przykład wyodrębnijmy pierwszy znak, Policzmy występowanie litery " e " i wielkie litery wyrażenia.

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)
Co zaskakujące, można uzyskać lepszą wydajność poprzez zapętlenie każdej wartości
%%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)

Kolejny przykład z fałszywymi danymi liczbowymi

Stwórz milion liczb losowych i przetestuj funkcję powers z góry.

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)

Przypisywanie każdej kolumny jest 25x szybsze i bardzo czytelny:

%%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)

Zrobiłem podobną odpowiedź z więcej szczegółów tutaj dlaczego apply jest zazwyczaj nie sposób iść.

 6
Author: Ted Petrou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-03 19:49:30

W 2018 roku używam apply() z argumentem 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')
 2
Author: ณัฐชนน นินยวี,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-17 08:45:29

Możesz zwrócić cały wiersz zamiast wartości:

df = df.apply(extract_text_features,axis = 1)

Gdzie funkcja zwraca wiersz

def extract_text_features(row):
      row['new_col1'] = value1
      row['new_col2'] = value2
      return row
 1
Author: Saket Bajaj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-24 19:06:57

Opublikowałem tę samą odpowiedź w dwóch innych podobnych pytaniach. Sposób, w jaki wolę to zrobić, to zawinąć wartości zwracane funkcji w szereg:

def f(x):
    return pd.Series([x**2, x**3])

A następnie użyj Zastosuj w następujący sposób, aby utworzyć oddzielne Kolumny:

df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)
 0
Author: Dmytro Bugayev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-29 03:17:24