IT

Pandas 데이터 프레임의 두 열에 함수를 적용하는 방법

lottoking 2020. 3. 25. 08:36
반응형

Pandas 데이터 프레임의 두 열에 함수를 적용하는 방법


df이있는 것으로 가정하십시오 'ID', 'col_1', 'col_2'. 그리고 함수를 정의합니다.

f = lambda x, y : my_function_expression.

이제 fto 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']

다음 apply은 데이터 프레임에서 사용하는 예제 입니다 axis = 1.

차이점은 함수에 두 개의 값을 전달하는 대신 fpandas Series 객체를 허용하도록 함수를 다시 작성한 다음 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

사용 사례에 따라 팬더 group객체를 만든 다음 apply그룹에서 사용 하는 것이 도움이되는 경우가 있습니다 .


간단한 해결책은 다음과 같습니다.

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

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]

흥미로운 질문입니다! 내 대답은 아래와 같습니다.

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

찾고있는 방법은 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 here). 그러나 다음과 같이 작동합니다.

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]

당신이 쓴 방식에는 두 개의 입력이 필요합니다. 오류 메시지를 보면 f에 두 개의 입력을 제공하지 않고 하나만 제공한다고 표시됩니다. 오류 메시지가 정확합니다.
불일치는 df [[ 'col1', 'col2']]가 두 개의 개별 열이 아닌 두 개의 열이있는 단일 데이터 프레임을 반환하기 때문입니다.

단일 입력을 취하도록 f를 변경하고 위의 데이터 프레임을 입력으로 유지 한 다음 함수 본문 에서 x, y로 나눕니다 . 그런 다음 필요한 것을 수행하고 단일 값을 반환하십시오.

구문은 .apply (f)이므로이 함수 시그니처가 필요합니다. f는 현재 f가 기대하는 두 가지가 아니라 단일 것 = 데이터 프레임을 취해야합니다.

f의 본문을 제공하지 않았기 때문에 더 이상 도움을 줄 수는 없지만 기본적으로 코드를 변경하거나 적용하지 않고 다른 방법을 사용하지 않고 탈출구를 제공해야합니다.


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]

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)

적용하지 않고 문제에 대답

applyaxis = 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)

팬더 또는 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]

귀하의 질문에 대한 나의 예 :

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

get_sublist함수 를 변경하고 싶지 않고 DataFrame의 apply메소드를 사용 하여 작업을 수행 하려고한다고 가정합니다 . 당신이 원하는 결과를 얻기 위하여는, 나는 두 도움말 기능을 작성했습니다 : get_sublist_listunlist. 함수 이름에서 알 수 있듯이 먼저 하위 목록을 가져오고 두 번째는 해당 목록에서 해당 하위 목록을 추출합니다. 마지막 으로이 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

함수 []를 묶는 데 사용하지 않으면 함수는 일반 목록을 반환하며 @Ted Petrou가 언급했듯이을 올립니다 .get_sublistget_sublist_listValueError: could not broadcast input array from shape (3) into shape (2)


거대한 데이터 세트가있는 경우 더 빠르고 더 빠른 (실행 시간) 방법을 사용하여 더 빠르게 사용할 수 있습니다.

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)

참고 URL : https://stackoverflow.com/questions/13331698/how-to-apply-a-function-to-two-columns-of-pandas-dataframe

반응형