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 ...)
.
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
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
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
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
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)
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.
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ść.
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')
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
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)
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