여러 개의 새 열을 만들려면 팬더 기능을 열에 적용 하시겠습니까?


215

팬더에서 이것을하는 방법 :

extract_text_features단일 텍스트 열에 함수 가 있으며 여러 출력 열을 반환합니다. 특히이 함수는 6 개의 값을 반환합니다.

함수가 작동하지만 출력이 올바르게 할당 될 수있는 적절한 반환 유형 (팬더 DataFrame / numpy 배열 / Python 목록)이없는 것 같습니다 df.ix[: ,10:16] = df.textcol.map(extract_text_features)

그래서 이것에df.iterrows() 따라 반복으로 되돌려 야한다고 생각 합니까?

업데이트 : 반복 df.iterrows()은 20 배 이상 느리므로 항복하여 함수를 6 개의 개별 .map(lambda ...)호출 로 분할했습니다 .

업데이트 2 :이 질문은 v0.11.0 주위에서 다시 요청 되었습니다 . 따라서 많은 질문과 답변이 그다지 관련성이 없습니다.


1
나는 당신이 쓴 방식으로 여러 번 할당 할 수 있다고 생각하지 않습니다 df.ix[: ,10:16]. merge데이터 세트에 기능 이 있어야한다고 생각합니다 .
— Zelazny7

1
훨씬 더 성능 이 좋은 솔루션을 원하시는 분들은 아래에서 사용하지 않는 솔루션을 확인하십시오apply
— Ted Petrou

팬더를 사용한 대부분의 숫자 연산은 벡터화 될 수 있습니다. 즉, 기존 반복보다 훨씬 빠릅니다. OTOH, 일부 연산 (예 : 문자열 및 정규식)은 본질적으로 벡터화하기 어렵습니다. 이 경우 데이터를 반복 하는 방법 을 이해 하는 것이 중요합니다 . 언제, 어떻게 데이터를 루핑해야하는지에 대한 자세한 정보는 팬더가있는 루프-언제주의해야합니까?를 참조하십시오. .
— cs95

@coldspeed : 주요 문제는 여러 옵션 중 더 높은 성능을 선택하는 것이 아니라 v0.11.0 주위에서 다시 작동하도록 팬더 구문과 싸우는 것 입니다.
— smci

실제로,이 의견은 반복적 인 솔루션을 찾고 있거나 더 잘 모르거나 자신이하는 일을 알고있는 미래의 독자를위한 것입니다.
— cs95

답변:


109

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
그냥 궁금해서, 이렇게함으로써 많은 메모리를 사용할 것으로 예상됩니까? 2.5mil 행을 보유하는 데이터 프레임 에서이 작업을 수행하고 있으며 메모리 문제가 거의 발생했습니다 (또한 1 열을 반환하는 것보다 훨씬 느립니다).
— Jeffrey04

2
'df.join (df.textcol.apply (lambs s : pd.Series ({'feature1 ': s + 1,'feature2 ': s-1}))')이 더 나은 옵션이라고 생각합니다.
— Shivam K. Thakkar

@ShivamKThakkar 왜 당신의 제안이 더 나은 선택이라고 생각하십니까? 생각하거나 더 적은 메모리 비용이 더 효율적입니까?
— tsando

1
속도와 필요한 메모리를 고려하십시오 : ys-l.github.io/posts/2015/08/28/how-not-to-use-pandas-apply
— Make42

190

나는 보통 이것을 사용하여 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

8
그러나 6이 아닌 이와 같이 50 개의 열을 추가하면 어떻게해야합니까?
— 최대

14
@maxtemp = list(zip(*df['num'].map(powers))); for i, c in enumerate(columns): df[c] = temp[c]
— ostrokach

8
@ostrokach 당신이 생각하는 것 같아요 for i, c in enumerate(columns): df[c] = temp[i]. 덕분에 나는 정말로 enumerate: D
— rocarvaj

4
이것은 지금까지 내가 찾은 가장 우아하고 읽기 쉬운 솔루션입니다. 성능 문제가 발생하지 않으면 관용구 zip(*df['col'].map(function))가 갈 수 있습니다.
— François Leblanc


84

이것이 내가 과거에 한 일입니다

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

새 열을 원본 데이터 프레임에 연결하기 위해 concat ()이 merge ()보다 단순 해 보입니다.
— cumin

2
좋은 대답은, 당신이 적용 이외의 열을 지정하는 경우 dict 또는 병합을 사용할 필요가 없습니다df[['col1', 'col2']] = df['col3'].apply(lambda x: pd.Series('val1', 'val2'))
— 매트

66

이것이 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

쓰지 말아야 할 것 : df = df.apply (example (df), axis = 1) 내가 틀렸다면 바로
— 고쳐줘

1
@ user299791, 아니요.이 경우 예제를 첫 번째 클래스 객체로 취급하므로 함수 자체를 전달합니다. 이 기능은 각 행에 적용됩니다.
— Michael David Watson

안녕 마이클, 당신의 대답은 내 문제에 도움이되었습니다. 확실히 솔루션은 원래 pandas의 df.assign () 메서드보다 낫습니다. cuz는 열당 한 번입니다. assign ()을 사용하여 두 개의 새 열을 만들려면 df1을 사용하여 df에서 작업하여 새 column1을 가져온 다음 df2를 사용하여 df1에서 작업하여 두 번째 새 열을 만들어야합니다 ... 이것은 매우 단조롭습니다. 그러나 당신의 방법은 내 생명을 구했습니다 !!! 감사!!!
— commentallez-vous

1
행당 한 번 열 할당 코드를 실행하지 않습니까? pd.Series({k:v})Ewan의 답변에서와 같이 a를 반환 하고 열 할당을 직렬화하는 것이 낫지 않습니까?
— Denis de Bernardy

그것은이 방법이 올 바르고있는 동안, 누군가를하는 데 도움이 직접 놀라 울 정도로 느리다 결국이 같은 행 업데이트 모든 제시된 솔루션의 또한 간단한 경우 - 느린 것보다 크기 순서를 '확장'+ pd.concat 솔루션 적용
— Dmytro Bugayev

31

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

6
요즘 그렇게하는 방법입니다!
— Make42

1
이것은 많은 다른 질문들과 달리 2020 년에 즉시 작동했습니다. 또한 pd.Series 성능 문제와 관련하여 항상 좋은 것을 사용하지는 않습니다.
— Théo Rubenach

1
이것은 좋은 해결책입니다. 유일한 문제는 새로 추가 된 2 개의 열 이름을 선택할 수 없다는 것입니다. 나중에 df.rename (columns = {0 : 'col1', 1 : 'col2'})
— pedram bashiri

2
@pedrambashiri 전달하는 함수가를 df.apply반환 dict하면 키에 따라 이름이 지정된 열이 나옵니다.
— 셉

25

그냥 사용 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")

4
옵션이 0.23의 새로운 기능 임을 지적하는 데 도움이됩니다 . 질문은 0.11
— smci

좋아, 이것은 간단하고 여전히 깔끔하게 작동합니다. 이것은 내가 찾던 것입니다. 감사합니다
— Isaac Sim

이전 답변을 복제합니다 : stackoverflow.com/a/52363890/823470
— tar

22

요약 : 몇 개의 열만 만들려면 다음을 사용하십시오.df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)

이 솔루션의 경우, 작성중인 새 열 수는 .apply () 함수에 대한 입력으로 사용하는 열 수와 같아야합니다. 다른 것을하고 싶다면 다른 답변을 살펴보십시오.

세부 사항 2 열 데이터 프레임이 있다고 가정합니다. 첫 번째 열은 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)

(명확하게하기 위해 :이 apply 함수는 서브 세트 데이터 프레임의 각 행에서 값을 받아서 목록을 리턴합니다.)

그러나 이렇게하면

df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)

[mean, sum] 목록을 포함하는 1 개의 새 열을 만들 것입니다.이 열에는 다른 Lambda / Apply가 필요하기 때문에 피하고 싶을 것입니다.

대신, 각 값을 자체 열로 분리하려고합니다. 이를 위해 한 번에 두 개의 열을 만들 수 있습니다.

df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']]
.apply(mean_and_sum(x),axis=1)

4
팬더 0.23의 경우 다음 구문을 사용해야합니다.df["mean"], df["sum"] = df[['height_at_age_10','height_at_age_20']] .apply(mean_and_sum(x),axis=1)
— SummerEla

이 기능은 오류를 일으킬 수 있습니다. 반환 기능은 다음과 같아야합니다. return pd.Series([mean,sum])
— Kanishk Mair

22

나를 위해 이것은 효과가 있었다 :

입력 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

13

나는 이것을하는 몇 가지 방법을 보았고 여기에 표시된 방법 (팬더 시리즈를 반환)이 가장 효율적인 것처럼 보이지 않습니다.

임의의 데이터로 구성된 큰 데이터 프레임으로 시작하는 경우 :

# 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.85ms)

내가 생각하면 일련의 튜플을 가져 와서 DataFrame으로 변환하는 것이 훨씬 효율적입니다. 작업에 오류가 있으면 사람들의 생각을 듣고 싶습니다.


이것은 정말 유용합니다! 함수 반환 직렬 메소드와 비교하여 30 배의 속도가 향상되었습니다.
— Pushkar Nimkar

9

수용되는 솔루션은 많은 데이터에 대해 매우 느릴 것입니다. 공감 수가 가장 많은 솔루션은 읽기가 약간 어렵고 숫자 데이터의 경우 속도가 느립니다. 각각의 새 열을 다른 열과 독립적으로 계산할 수 있다면 사용하지 않고 각 열을 직접 할당합니다.apply .

가짜 문자 데이터의 예

DataFrame에서 100,000 개의 문자열 만들기

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)

가짜 숫자 데이터가있는 다른 예

백만 개의 난수를 만들고 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.


8

두 개의 다른 유사한 질문에 동일한 답변을 게시했습니다. 내가 이것을 선호하는 방법은 함수의 반환 값을 연속적으로 정리하는 것입니다.

def f(x):
    return pd.Series([x**2, x**3])

그런 다음 다음과 같이 적용을 사용하여 별도의 열을 만듭니다.

df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)

1

값 대신 전체 행을 반환 할 수 있습니다.

df = df.apply(extract_text_features,axis = 1)

함수가 행을 반환하는 곳

def extract_text_features(row):
      row['new_col1'] = value1
      row['new_col2'] = value2
      return row

아니요 extract_text_featuresdf의 모든 열에 적용하고 싶지는 않지만 텍스트 열에 만 적용 됩니다df.textcol
— smci

-2
def myfunc(a):
    return a * a

df['New Column'] = df['oldcolumn'].map(myfunc))

이것은 나를 위해 일했습니다. 처리 된 이전 열 데이터로 새 열이 생성됩니다.



이것은 '여러 개의 새 열'을 반환하지 않으므로 질문에 대답하지 않습니다. 삭제 하시겠습니까?
— smci
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.