Як застосувати функцію до двох стовпців фрейму даних Pandas


368

Припустимо, у мене є df який має стовпці 'ID', 'col_1', 'col_2'. І я визначаю функцію:

f = lambda x, y : my_function_expression.

Тепер я хочу застосувати fдо dfдвох стовпців, 'col_1', 'col_2'щоб обчислити новий стовпець 'col_3', як-от так:

df['col_3'] = df[['col_1','col_2']].apply(f)  
# Pandas gives : TypeError: ('<lambda>() takes exactly 2 arguments (1 given)'

Як зробити ?

** Додайте зразок деталей, як показано нижче ***

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

#df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1)
# expect above to output df as below 

  ID  col_1  col_2            col_3
0  1      0      1       ['a', 'b']
1  2      2      4  ['c', 'd', 'e']
2  3      3      5  ['d', 'e', 'f']

4
чи можна застосувати f безпосередньо до стовпців: df ['col_3'] = f (df ['col_1'], df ['col_2'])
— btel

1
Було б корисно знати, що fробиться
— tehmisvh

2
ні, df ['col_3'] = f (df ['col_1'], df ['col_2']) не працює. Для f приймає лише скалярний вхід, а не векторні входи. Гаразд, ви можете припустити, що f = лямбда х, у: х + у. (звичайно, моя справжня f не така проста, інакше я можу безпосередньо df ['col_3'] = df ['col_1'] + df ['col_2'])
— bigbug

1
Я знайшов відповідні запитання і відповіді за нижньою URL-адресою, але моя проблема - обчислення нового стовпця двома існуючими стовпцями, а не 2 з 1. stackoverflow.com/questions/12356501 / ...
— bigbug

Я думаю, що моя відповідь stackoverflow.com/a/52854800/5447172 відповідає на це самим пітонічним / панданським способом, не маючи обхідних шляхів чи числового індексування. Він дає саме той результат, який вам потрібен у вашому прикладі.
— ajrwhite

Відповіді:


291

Ось приклад використання applyу кадрі даних, до якого я дзвонюaxis = 1 .

Зауважте, різниця полягає в тому, що замість того, щоб намагатися передати два значення функції f, перепишіть функцію, щоб прийняти об'єкт серії pandas, а потім індексуйте серію, щоб отримати необхідні значення.

In [49]: df
Out[49]: 
          0         1
0  1.000000  0.000000
1 -0.494375  0.570994
2  1.000000  0.000000
3  1.876360 -0.229738
4  1.000000  0.000000

In [50]: def f(x):    
   ....:  return x[0] + x[1]  
   ....:  

In [51]: df.apply(f, axis=1) #passes a Series object, row-wise
Out[51]: 
0    1.000000
1    0.076619
2    1.000000
3    1.646622
4    1.000000

Залежно від випадку використання, іноді корисно створити об’єкт панди group, а потім використовувати applyв групі.


Так, я спробував застосувати застосовувати, але не можу знайти дійсний синтаксичний вираз. І якщо кожен рядок df унікальний, все-таки використовувати groupby?
— bigbug

Додав приклад до моєї відповіді, сподіваюся, що це робить те, що ви шукаєте. Якщо ні, будь ласка, надайте більш конкретну прикладну функцію, оскільки sumвона успішно вирішена будь-яким із запропонованих до цього часу методів.
— Аман

1
Ви б приклеїли свій код? Я переписав функцію: def get_sublist (x): повернути мій список [x [1]: x [2] + 1] і df ['col_3'] = df.apply (get_sublist, ось = 1) дає 'ValueError: операнди могли не транслюватись разом із фігурами (2) (3) '
— bigbug

3
@Aman: з Pandas версії 0.14.1 (і, можливо, раніше), використання може також використовувати лямбда-вираз. Дайте dfвизначений вами об'єкт, інший підхід (з еквівалентними результатами)df.apply(lambda x: x[0] + x[1], axis = 1) .
— Jubbles

2
@CanCeylan ви можете просто використовувати імена стовпців у функції замість індексів, тоді вам не потрібно турбуватися про зміну замовлення або отримати індекс за назвою, наприклад, див. Stackoverflow.com/questions/13021654/…
— Давос

165

Існує чистий однолінійний спосіб зробити це в Pandas:

df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)

Це дозволяє fбути визначеною користувачем функцією з кількома вхідними значеннями і використовує (безпечні) назви стовпців, а не (небезпечні) числові індекси для доступу до стовпців.

Приклад з даними (на основі оригінального запитання):

import pandas as pd

df = pd.DataFrame({'ID':['1', '2', '3'], 'col_1': [0, 2, 3], 'col_2':[1, 4, 5]})
mylist = ['a', 'b', 'c', 'd', 'e', 'f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

df['col_3'] = df.apply(lambda x: get_sublist(x.col_1, x.col_2), axis=1)

Вихід print(df):

  ID  col_1  col_2      col_3
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]

Якщо назви стовпців містять пробіли або поділяють ім’я з наявним атрибутом фрейму даних, ви можете проіндексувати квадратними дужками:

df['col_3'] = df.apply(lambda x: f(x['col 1'], x['col 2']), axis=1)

2
Зауважте, що якщо використовується axis=1та ви називаєте стовпець, nameвін фактично не поверне ваші дані стовпця, але index. Подібно до отримання nameв groupby(). Я вирішив це, перейменувавши свою колонку.
— Том

2
ОСЬ ВОНО! Я просто не розумів, що ви можете вставити визначені користувачем функції з декількома вхідними параметрами в лямбда. Важливо зазначити (я думаю), що ви використовуєте DF.apply (), а не Series.apply (). Це дозволяє індексувати df за допомогою двох потрібних стовпців і передавати весь стовпець у функцію, але оскільки ви використовуєте Apply (), він застосовує функцію в елементному порядку вниз по всьому стовпцю. Блискуче! Дякую за публікацію!
— Дані-

1
ОКОНЧНО! Ти врятував мій день!
— Містеріо

Я вважаю, що запропонований спосіб зробити це df.loc [:, 'new col'] = df.apply .....
— valearner

@valearner Я не думаю, що .locв цьому прикладі є причина . Це може знадобитися, якщо ви адаптуєте це до іншого завдання (наприклад, робота з фрагментами).
— ajrwhite

86

Просте рішення:

df['col_3'] = df[['col_1','col_2']].apply(lambda x: f(*x), axis=1)

1
Чим ця відповідь відрізняється від підходу в питанні: df ['col_3'] = df [['col_1', 'col_2']]. застосувати (f) лише для підтвердження, підхід у питанні не спрацював, оскільки плакат не вказав цю вісь = 1, за замовчуванням - ось = 0?
— Втрачено1

1
Ця відповідь можна порівняти з відповіддю @ Анмана, але трохи меншою. Він будує анонімну функцію, яка бере ітерабельний, і розпаковує її, перш ніж передати її у функцію f.
— Тяо

39

Цікаве запитання! моя відповідь як нижче:

import pandas as pd

def sublst(row):
    return lst[row['J1']:row['J2']]

df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]})
print df
lst = ['a','b','c','d','e','f']

df['J3'] = df.apply(sublst,axis=1)
print df

Вихід:

  ID  J1  J2
0  1   0   1
1  2   2   4
2  3   3   5
  ID  J1  J2      J3
0  1   0   1     [a]
1  2   2   4  [c, d]
2  3   3   5  [d, e]

Я змінив ім'я стовпця на ID, J1, J2, J3, щоб забезпечити ідентифікацію <J1 <J2 <J3, тому відображення стовпця в правильній послідовності.

Ще одна коротка версія:

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]})
print df
lst = ['a','b','c','d','e','f']

df['J3'] = df.apply(lambda row:lst[row['J1']:row['J2']],axis=1)
print df

23

Метод, який ви шукаєте - Series.combine. Однак, схоже, слід дотримуватися певної обережності навколо типів даних. У вашому прикладі ви (як я робив тестування відповіді) наївно закликали

df['col_3'] = df.col_1.combine(df.col_2, func=get_sublist)

Однак це призводить до помилки:

ValueError: setting an array element with a sequence.

Моя найкраща здогадка полягає в тому, що, здається, очікується, що результат буде такого ж типу, як і ряд, що викликає метод (тут df.col_1). Однак наступні роботи:

df['col_3'] = df.col_1.astype(object).combine(df.col_2, func=get_sublist)

df

   ID   col_1   col_2   col_3
0   1   0   1   [a, b]
1   2   2   4   [c, d, e]
2   3   3   5   [d, e, f]

12

Спосіб, який ви написали, потрібен два входи. Якщо ви подивитесь на повідомлення про помилку, воно говорить про те, що ви не надаєте два входи до f, а лише один. Повідомлення про помилку правильне.
Невідповідність полягає в тому, що df [['col1', 'col2']] повертає один кадр даних з двома стовпцями, а не двома окремими стовпцями.

Потрібно змінити свій f, щоб він займав один вхід, тримати вищевказаний кадр даних як вхідний, а потім розбити його на x, y всередині функції функції. Потім зробіть все, що вам потрібно, і поверніть єдине значення.

Цей підпис функції вам потрібен, оскільки синтаксис є .apply (f) Отже, f має взяти одну річ = dataframe, а не дві речі, що очікує ваш поточний f.

Оскільки ви не надали основу f, я не можу допомогти більш докладно - але це повинно забезпечити вихід без принципової зміни коду чи використання інших методів, а не застосовувати


12

Я буду голосувати за np.vectorize. Це дозволяє просто стріляти через x кількість стовпців і не мати справу з фреймом даних у функції, тому це чудово для функцій, якими ви не керуєте, або виконувати щось на зразок надсилання 2 стовпців і константи у функцію (наприклад, col_1, col_2, 'foo').

import numpy as np
import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

#df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1)
# expect above to output df as below 

df.loc[:,'col_3'] = np.vectorize(get_sublist, otypes=["O"]) (df['col_1'], df['col_2'])


df

ID  col_1   col_2   col_3
0   1   0   1   [a, b]
1   2   2   4   [c, d, e]
2   3   3   5   [d, e, f]

1
Це насправді не відповідає на питання, використовуючи панди.
— mnky9800n

18
Питання "Як застосувати функцію до двох стовпців фрейму даних Pandas", а не "Як застосувати функцію до двох стовпців фрейму даних Pandas, використовуючи лише методи Pandas", а numpy - це залежність Панди, тому вам доведеться все-таки встановити її, тому це здається дивним запереченням.
— Трей Уоллес

12

Повернення списку з apply- це небезпечна операція, оскільки отриманий об'єкт не гарантовано є ні Series, ні DataFrame. І у певних випадках можуть бути винятки. Розглянемо простий приклад:

df = pd.DataFrame(data=np.random.randint(0, 5, (5,3)),
                  columns=['a', 'b', 'c'])
df
   a  b  c
0  4  0  0
1  2  0  1
2  2  2  2
3  1  2  2
4  3  0  0

Можливі три результати із поверненням списку apply

1) Якщо довжина повернутого списку не дорівнює кількості стовпців, то повертається Серія списків.

df.apply(lambda x: list(range(2)), axis=1)  # returns a Series
0    [0, 1]
1    [0, 1]
2    [0, 1]
3    [0, 1]
4    [0, 1]
dtype: object

2) Коли довжина повернутого списку дорівнює кількості стовпців, тоді повертається DataFrame і кожен стовпець отримує відповідне значення у списку.

df.apply(lambda x: list(range(3)), axis=1) # returns a DataFrame
   a  b  c
0  0  1  2
1  0  1  2
2  0  1  2
3  0  1  2
4  0  1  2

3) Якщо довжина повернутого списку дорівнює кількості стовпців для першого рядка, але має принаймні один рядок, у якому в списку є інша кількість елементів, ніж кількість стовпців, підвищується ValueError.

i = 0
def f(x):
    global i
    if i == 0:
        i += 1
        return list(range(3))
    return list(range(4))

df.apply(f, axis=1) 
ValueError: Shape of passed values is (5, 4), indices imply (5, 3)

Відповідь на проблему без застосування

Використання applyосі = 1 дуже повільне. Можна досягти набагато кращої продуктивності (особливо на великих наборах даних) за допомогою основних ітеративних методів.

Створіть більший кадр даних

df1 = df.sample(100000, replace=True).reset_index(drop=True)

Хронометраж

# apply is slow with axis=1
%timeit df1.apply(lambda x: mylist[x['col_1']: x['col_2']+1], axis=1)
2.59 s ± 76.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# zip - similar to @Thomas
%timeit [mylist[v1:v2+1] for v1, v2 in zip(df1.col_1, df1.col_2)]  
29.5 ms ± 534 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

@Тома відповідь

%timeit list(map(get_sublist, df1['col_1'],df1['col_2']))
34 ms ± 459 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
Приємно бачити настільки детальні відповіді, звідки можна навчитися.
— Андреа Моро

7

Я впевнений, що це не так швидко, як рішення, що використовують операції Pandas або Numpy, але якщо ви не хочете перезаписувати свою функцію, ви можете використовувати map. Використовуючи вихідні приклади даних -

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

df['col_3'] = list(map(get_sublist,df['col_1'],df['col_2']))
#In Python 2 don't convert above to list

Ми могли передавати стільки аргументів, скільки хотіли, у цю функцію. Вихід - це те, що ми хотіли

ID  col_1  col_2      col_3
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]

1
Це насправді набагато швидше , відповіді на ці питання , що використання applyзaxis=1
— Тед Petrou

2

Мій приклад до ваших питань:

def get_sublist(row, col1, col2):
    return mylist[row[col1]:row[col2]+1]
df.apply(get_sublist, axis=1, col1='col_1', col2='col_2')

2

Якщо у вас є величезний набір даних, ви можете використовувати простий, але швидший (час виконання) спосіб зробити це за допомогою swifter:

import pandas as pd
import swifter

def fnc(m,x,c):
    return m*x+c

df = pd.DataFrame({"m": [1,2,3,4,5,6], "c": [1,1,1,1,1,1], "x":[5,3,6,2,6,1]})
df["y"] = df.swifter.apply(lambda x: fnc(x.m, x.x, x.c), axis=1)

1

Я припускаю, що ви не хочете змінювати get_sublistфункції, а просто хочете використовувати applyметод DataFrame, щоб виконати цю роботу. Щоб отримати бажаний результат, я написав дві функції допомоги: get_sublist_listі unlist. Як випливає з назви функції, спочатку отримайте список підспіву, другий витягніть його з цього списку. Нарешті, нам потрібно викликати applyфункцію, щоб df[['col_1','col_2']]згодом застосувати ці дві функції до DataFrame.

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

def get_sublist_list(cols):
    return [get_sublist(cols[0],cols[1])]

def unlist(list_of_lists):
    return list_of_lists[0]

df['col_3'] = df[['col_1','col_2']].apply(get_sublist_list,axis=1).apply(unlist)

df

Якщо ви не використовуєте []для вкладення get_sublistфункції, get_sublist_listфункція поверне звичайний список, він збільшиться ValueError: could not broadcast input array from shape (3) into shape (2), як згадував @Ted Petrou.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.