Как применить функцию к двум столбцам Pandas dataframe - программирование
Подтвердить что ты не робот

Как применить функцию к двум столбцам Pandas dataframe

Предположим, что у меня есть df, который имеет столбцы из 'ID', 'col_1', 'col_2'. И я определяю функцию:

f = lambda x, y : my_function_expression.

Теперь я хочу применить f to 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']
4b9b3361

Ответ 1

Вот пример использования apply на фрейме данных, который я вызываю с помощью axis = 1.

Обратите внимание, что разница заключается в том, что вместо того, чтобы пытаться передать два значения функции f, перепишите функцию, чтобы принять объект pandas Series, а затем проиндексируйте Серию, чтобы получить необходимые значения.

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

В зависимости от вашего варианта использования иногда полезно создать объект pandas group, а затем использовать apply в группе.

Ответ 2

Простое решение:

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

Ответ 3

В 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]

Ответ 4

Интересный вопрос! мой ответ ниже:

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, чтобы убедиться, что ID < 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

Ответ 5

Метод, который вы ищете, - это 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]

Ответ 6

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

Вам нужно изменить свой f так, чтобы он принимал один вход, сохраните вышеуказанный фрейм данных как входной сигнал, а затем разделите его на x, y внутри тела функции. Затем сделайте все, что вам нужно, и верните одно значение.

Вам нужна эта подпись функции, потому что синтаксис -.apply(f) Поэтому f нужно взять единственную вещь = dataframe, а не две вещи, которые вы ожидаете от своего текущего f.

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

Ответ 7

Я собираюсь сделать голосование за np.vectorize. Это позволяет вам просто снимать по числу столбцов и не обрабатывать фрейм данных в функции, поэтому отлично подходит для функций, которые вы не контролируете или не выполняете что-то вроде отправки 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]

Ответ 8

Возврат списка из apply является опасной операцией, так как результирующий объект не гарантируется как серией, так и 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)

Ответ 9

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

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]

Ответ 10

Мой пример на ваши вопросы:

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

Ответ 11

Я полагаю, вы не хотите, чтобы изменить get_sublist функции, и просто хотите использовать DataFrame apply метод, чтобы сделать работу. Чтобы получить get_sublist_list результат, я написал две справочные функции: 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, то функция get_sublist_list вернет простой список и вызовет ValueError: could not broadcast input array from shape (3) into shape (2) как @Ted Petrou упомянул.

Ответ 12

Если у вас огромный набор данных, вы можете использовать простой, но более быстрый (время выполнения) способ сделать это с помощью 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)