Як застосувати функцію до двох стовпців фрейму даних 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.