2017-12-29 9 views
0

목표는 DataFrame 개체를 가져 와서 그룹별로 계산하는 여러 열을 추가하는 것입니다. 그러나이 계산은 직관적으로 벡터화 할 수 없습니다 (if 문과 누적 합계가 포함됨).복수 열 출력이있는 DataFrame GroupBy

나는 보이는 코드를 실행하는 것 인 R data.table 배경에서오고 같은 :

DT[,c('newcol1','newcol2'):=f(.SD),by=groupvar] 
-data.table 하위 groupvar가 그룹화 변수이며, 함수 f가에 소요

(그룹으로 나누어) 그룹의 길이와 동일한 길이의 두 배열을 가진 목록을 반환합니다. 이 경우 할당의 부작용 : = newcol1과 newcol2라는 두 개의 새 열을 원래의 data.table DT에 추가합니다.

팬더 문서를 사용해 보았지만이 작업을 복제하는 방법이 아직 불분명합니다 (예 : 내 함수 f가 DataFrames를 반환하거나 시리즈를 사용해야하는 경우).

여기에 내 초기 DF입니다 :

import pandas as pd 
df=pd.DataFrame({'id': [1, 1, 1, 1, 2, 2, 2, 2],'time':[1,2,3,4,1,2,3,4],'choice':['a','a','b','a','b','b','b','b']}) 

내가 두 개의 열 'A'와 'B'에 추가 할 것 같은 그들은 해당 ID에 의해 선택 'A'또는 'B'의 누적 수를 센다 그 기간 이전에 나는 대략 그룹별로 올바른 작업을 수행하는 기능을 작성했습니다

dffinal=pd.DataFrame({'id': [1, 1, 1, 1, 2, 2, 2, 2],'time' [1,2,3,4,1,2,3,4],'choice':['a','a','b','a','b','b','b','b'],'a':[0,1,2,2,0,0,0,0],'b'=[0,0,0,1,0,1,2,3]}) 

(그것을 가정하는 것은 이미 시간으로 정렬됩니다) :

def cumulativechoice(df): 
    length=df.shape[0] 
    cols=['a','b'] 
    for x in cols: 
     df[x]=0 
    for x in cols: 
     counter=0 
     for y in range(length): 
      df.loc[y,x]=counter 
      if df.loc[y,'choice']==x: 
       counter=counter+1 
    return df[cols] 

기능은 잘 작동 내 원하는 출력이 있습니다 누적 선택 (subdf)을 실행하면 subdf가 하나의 ID에 대한 하위 DataFrame이고 df.groupby ('id')를 시도하면 연결이 끊어집니다. '누락 된 축에서 색인을 다시 만들 수 없습니다.'라는 오류 메시지가 적용됩니다 (cumulativechoice). 여기서 내가 뭘 잘못하고 있니?

편집 : 더 일반적으로 내 질문은 내 cumulativechoice 내역에 관한 것이 아니라 '올바른'split-apply-combine 수식은 1) 그룹으로 나누기, 2) 적용 여러 dicts/DataFrame 등을 생성하는 함수 및 3) 최종 결과가 출력에 여러 열을 추가 했으므로 다시 결합하여 특별한 경우에는 '변환'만큼 간단하지 않습니다.

답변

0

흠, 이것은 다소 복잡하지만 어렵지 않습니다. 이 간단한 것을 만들기 위해 pd.get_dummies을 활용할 수 있습니다.


df = df.set_index('id') def f(x): return x.shift().fillna(0).cumsum().astype(int) v = pd.get_dummies(df.choice).groupby(level=0).apply(f) pd.concat([v, df], 1).reset_index() id a b choice time 0 1 0 0 a 1 1 1 1 0 a 2 2 1 2 0 b 3 3 1 2 1 a 4 4 2 0 0 b 1 5 2 0 1 b 2 6 2 0 2 b 3 7 2 0 3 b 4 
세부

첫째, 인덱스를 설정합니다.

df = df.set_index('id') 

가져 OHEs get_dummies에서 - 이제

i = pd.get_dummies(df.choice) 
i 

    a b 
id  
1 1 0 
1 1 0 
1 0 1 
1 1 0 
2 0 1 
2 0 1 
2 0 1 
2 0 1 

, groupbyID, shift 아래 (1)에 의해 각 값은 cumsum를 찾아 다시 변환.

pd.concat([v, df], 1) 

    a b choice time 
id     
1 0 0  a  1 
1 1 0  a  2 
1 2 0  b  3 
1 2 1  a  4 
2 0 0  b  1 
2 0 1  b  2 
2 0 2  b  3 
2 0 3  b  4 

을 그리고 후에 인덱스를 재설정 -

v = i.groupby(level=0).apply(lambda x: 
     x.shift().fillna(0).cumsum().astype(int)) 
v 

    a b 
id  
1 0 0 
1 1 0 
1 2 0 
1 2 1 
2 0 0 
2 0 1 
2 0 2 
2 0 3 

지금, 그것은 결과를 합치의 문제입니다. 그 출력이 잘못 아닌가요

df[['a', 'b']] = v 
df 

    choice time a b 
id     
1  a  1 0 0 
1  a  2 1 0 
1  b  3 2 0 
1  a  4 2 1 
2  b  1 0 0 
2  b  2 0 1 
2  b  3 0 2 
2  b  4 0 3 
+0

이것은 좋게 보이고 원하는 것을 제공하지만 두 번째 DataFrame을 만들지 않고 수동으로 다시 연결하지 않고이 작업을 수행 할 수있는 방법이 있습니다. 실제로 가능한지 모르겠습니다. 나는 여전히 팬더를 배우고 있기 때문에 새로운 컬럼을 만들고 '표준'분할 적용 적용 공식을 다시 연결하는 방법입니까? – Ray

+0

@ 레이 예, 또 다른 방법이 있는데, 이는 더 저렴합니다. 이를 슬라이스 할당이라고합니다. 무엇이든하기 전에 색인을 ID로 설정해야합니다 (내 대답 편집 참조). 또한 S-A-C 패러다임은 데이터 프레임을 그룹으로 분할하고, 각 그룹에서 작동하며 (벡터화되기를 희망 함) 결과를 결합하는 것을 의미합니다. 자세한 내용은 여기에 있습니다. https://pandas.pydata.org/pandas-docs/stable/groupby.html –

+0

설명해 주셔서 감사합니다. 이것은 약간의 멍청한 질문이지만, 결국이 문제와 같은 group-by 연산을 사용하여 n 개의 새로운 열을 추가하려는 일반적인 문제에서, 어떤 종류의 객체가 내 함수 f (x)에서 그룹 출력? DataFrame, 목록이있는 사전 등이되어야합니까? 나는 R에서 각 그룹에 대해 data.frame 스타일 객체 (또는 파이썬리스트의 아날로그)를 출력했다면 최종 출력의 모든 그룹에 걸쳐 그것들을 나를 "쌓을 것"을 알았지 만 동등한 결과는 무엇입니까? 팬더를 사용할 때 각 그룹을 목표로해야합니까? – Ray

0

return df[cols]에서 return df으로 변경하면이 오류가 발생하지 않지만 문제가 완전히 해결되지는 않습니다. 코드에서 사용하는 for-loop은 데이터 프레임을 반복하는 적절한 방법이 아닙니다. 대신 우리는

# set location where true == 1 
df.loc[df.choice == 'a','a'] = 1 
df.loc[df.choice == 'b','b'] = 1 

#do a cumsum on new columns 
df.fillna(0).groupby('id')['a','b'].cumsum() 

당신에게 대신 1을 시작 아래의 값을 제공합니다 .. 어쩌면 이런 일이 좀 더 쉽게 작동합니다, 그러나 쓸모없는 코드를

def cumulativechoice(df): 
    cols=['a','b'] 
    for z in cols: 
     df[z]=0 
    for x in cols: 
     counter=0 
     for index,row in df.iterrows(): 
      df.loc[index,x]=counter 
      if row['choice']==x: 
       counter=counter+1 
    return df[cols] #<- this for just 'a' & 'b' or return df for entire df 

iterrows()로 변경하고 제거 할 수 있습니다 제로,하지만 필요하다면 오프셋 할 수 있습니다 ....

a b 
0 1 0 
1 2 0 
2 2 1 
3 3 1 
4 0 1 
5 0 2 
6 0 3 
7 0 4 
+0

-

연결에 대한 대안은 슬라이스 할당 될 것이다? –

+0

출력물이 내가 원하는 것만은 아니지만 큰 문제는 아닙니다. 내 진짜 문제는 올바른 '분할 적용 - 결합'구문/프레임 워크에 여러 가지 추가 열을 추가하는 작업이 포함됩니다. – Ray