Применить функцию pandas к столбцу для создания нескольких новых столбцов?
Как это сделать в пандах:
У меня есть функция extract_text_features
для одного текстового столбца, возвращающая несколько выходных столбцов. В частности, функция возвращает 6 значений.
Функция работает, однако, похоже, нет правильного возвращаемого типа (pandas DataFrame/numpy array/Python list), так что вывод может быть правильно назначен df.ix[: ,10:16] = df.textcol.map(extract_text_features)
Так что я думаю, что мне нужно вернуться к итерации с df.iterrows()
, в соответствии с этим?
ОБНОВЛЕНИЕ: Итерации с df.iterrows()
по крайней мере в 20 раз медленнее, поэтому я сдался и разделил функцию на шесть отдельных вызовов .map(lambda...)
.
ОБНОВЛЕНИЕ 2: этот вопрос был задан около v0.11.0. Следовательно, большая часть вопроса и ответов не слишком актуальны.
Ответы
Ответ 1
Построив ответ user1827356, вы можете выполнить назначение за один проход, используя 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
РЕДАКТИРОВАТЬ: Обратите внимание на огромное потребление памяти и низкую скорость: https://ys-l.github.io/posts/2015/08/28/how-not-to-use-pandas-apply/ !
Ответ 2
Обычно я делаю это с помощью 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
Ответ 3
Это то, что я делал в прошлом
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
Редактирование полноты
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
Ответ 4
Это правильный и простой способ сделать это для 95% случаев:
>>> 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
Ответ 5
Резюме: Если вы хотите создать несколько столбцов, используйте df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)
Для этого решения количество новых создаваемых столбцов должно быть равно числу столбцов, которые вы используете для ввода функции.apply(). Если вы хотите сделать что-то еще, взгляните на другие ответы.
Подробности. Скажем, у вас есть двухстоечный файл. Первый столбец - высота человека, когда они равны 10; второй - это высота человека, когда ему 20.
Предположим, вам нужно рассчитать как среднее значение высоты каждого человека, так и сумму каждой высоты человека. Это два значения для каждой строки.
Вы можете сделать это с помощью следующей, скоро будущей функции:
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]
Вы можете использовать эту функцию следующим образом:
df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)
(Чтобы быть ясным: эта применимая функция принимает значения из каждой строки в подмножестве данных и возвращает список.)
Однако, если вы это сделаете:
df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)
вы создадите 1 новый столбец, содержащий списки [mean, sum], которые вы, вероятно, хотите избежать, потому что для этого потребуется другое Lambda/Apply.
Вместо этого вы хотите вывести каждое значение в свой собственный столбец. Для этого вы можете создать сразу два столбца:
df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']]
.apply(mean_and_sum(x),axis=1)
Ответ 6
Я рассмотрел несколько способов сделать это, и метод, показанный здесь (возвращение серии pandas), кажется, не является наиболее эффективным.
Если мы начнем с большого массива данных случайных данных:
# 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'
Пример, показанный здесь:
# 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 петель, лучше всего 3: 2,77 с за цикл
Альтернативный метод:
# 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 петель, лучше всего 3: 8,85 мс за цикл
По моим расчетам, гораздо эффективнее взять ряд кортежей, а затем преобразовать их в DataFrame. Мне было бы интересно услышать, как люди думают, если есть ошибка в моей работе.
Ответ 7
В 2018 году я использую apply()
с аргументом 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')
Ответ 8
Для меня это сработало:
Вход df
df = pd.DataFrame({'col x': [1,2,3]})
col x
0 1
1 2
2 3
функция
def f(x):
return pd.Series([x*x, x*x*x])
Создайте 2 новых столбца:
df[['square x', 'cube x']] = df['col x'].apply(f)
Выход:
col x square x cube x
0 1 1 1
1 2 4 8
2 3 9 27
Ответ 9
Принятое решение будет очень медленным для большого количества данных. Решение с наибольшим количеством upvotes немного сложно читать, а также замедлять числовые данные. Если каждый новый столбец можно вычислить независимо от других, я просто назначил бы каждый из них напрямую, не используя apply
.
Пример с поддельными символьными данными
Создайте 100 000 строк в 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
Скажем, мы хотели извлечь некоторые текстовые функции, как это было сделано в исходном вопросе. Например, пусть выберем первый символ, посчитаем появление буквы "e" и запишем эту фразу.
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
Задержка
%%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)
Удивительно, но вы можете получить более высокую производительность, пройдя через каждое значение
%%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)
Другой пример с поддельными числовыми данными
Создайте 1 миллион случайных чисел и проверьте функцию powers
сверху.
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)
Назначение каждого столбца на 25 раз быстрее и очень читаемо:
%%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)
Я сделал аналогичный ответ с подробнее здесь о том, почему apply
обычно не подходит.
Ответ 10
Выложили один и тот же ответ в двух других подобных вопросах. Способ, которым я предпочитаю это делать, состоит в том, чтобы обернуть возвращаемые значения функции из серии:
def f(x):
return pd.Series([x**2, x**3])
А затем для создания отдельных столбцов используйте следующие правила:
df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)
Ответ 11
Просто используйте result_type="expand"
df = pd.DataFrame(np.random.randint(0,10,(10,2)), columns=["random", "a"])
df[["sq_a","cube_a"]] = df.apply(lambda x: [x.a**2, x.a**3], axis=1, result_type="expand")
Ответ 12
вы можете вернуть всю строку вместо значений:
df = df.apply(extract_text_features,axis = 1)
где функция возвращает строку
def extract_text_features(row):
row['new_col1'] = value1
row['new_col2'] = value2
return row