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


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.