2017-02-10 2 views
2

해당 열 중 하나에서 각 요소가 목록이라는 데이터 집합이 있습니다. 모든 목록 요소가 자신의 행을 가지도록 평면화하려고합니다.pandas 데이터 프레임을 효율적으로 확장/병합하는 방법

iterrows, dictappend (아래 참조)으로 해결할 수 있었지만 실제 DF는 너무 느립니다. 일을 더 빨리 할 수있는 방법이 있습니까?

더 많은 이해가된다면 요소 당 목록을 다른 형식 (아마도 계층 적 df?)으로 바꾸는 것이 좋습니다.

EDIT : 열이 많으며 나중에 변경 될 수도 있습니다. 내가 아는 유일한 것은 필드 열이 있다는 것입니다.

import StringIO 
df = pd.read_csv(StringIO.StringIO(""" 
id|name|fields 
1|abc|[qq,ww,rr] 
2|efg|[zz,xx,rr] 
"""), sep='|') 
df.fields = df.fields.apply(lambda s: s[1:-1].split(',')) 
print df 

결과 DF :

id name  fields 
0 1 abc [qq, ww, rr] 
1 2 efg [zz, xx, rr] 

내 (느리게) 솔루션 :

new_df = pd.DataFrame(index=[], columns=df.columns) 

for _, i in df.iterrows(): 
    flattened_d = [dict(i.to_dict(), fields=c) for c in i.fields] 
    new_df = new_df.append(flattened_d) 
내가 놀 수있는 DF을 만들어 내 솔루션

최소한의 예에서 dict을 사용하는 이유

결과 :

id name fields 
0 1.0 abc  qq 
1 1.0 abc  ww 
2 1.0 abc  rr 
0 2.0 efg  zz 
1 2.0 efg  xx 
2 2.0 efg  rr 

답변

1

당신은 fieldspandas.Series을 적용하고 id에 병합 및 name과 같이 여러 컬럼에 fields 열에서 목록을 깰 수 : 그럼 당신은 set_index를 사용하여 생성 된 새 열 용융 수

cols = df.columns[df.columns != 'fields'].tolist() # adapted from @jezrael 
df = df[cols].join(df.fields.apply(pandas.Series)) 

df = df.set_index(cols).stack().reset_index() 

마지막으로, 중복 열 GE 드롭 : stack 다음 인덱스를 다시 설정하라는 reset_index에 의해 nerated와 "필드"로 생성 된 열 이름을 변경 :

df = df.drop(df.columns[-2], axis=1).rename(columns={0: 'field'}) 
+0

첫 번째 명령이 실패합니다. 오류는'MergeError : 병합을 수행 할 공통 열이 없음' – yuval

+0

죄송합니다. 인덱스 값을 기반으로 작동하는'join'을 사용하려고했습니다. 내 대답을 바로 잡았어. – cmaher

+0

아직도 작동하지 않습니다.결과는 다음과 같습니다 (한 줄로 표시). 'id name level_2 0 0 1 abc fields [qq, ww, rr] 1 2 efg fields [zz, xx, rr] ' – yuval

4

당신은 더 나은 성능을 위해 numpy를 사용할 수 있습니다

두 솔루션은 주로 numpy.repeat 사용합니다.

from itertools import chain 

vals = df.fields.str.len() 
df1 = pd.DataFrame({ 
     "id": np.repeat(df.id.values,vals), 
     "name": np.repeat(df.name.values, vals), 
     "fields": list(chain.from_iterable(df.fields))}) 
df1 = df1.reindex_axis(df.columns, axis=1) 
print (df1) 
    id name fields 
0 1 abc  qq 
1 1 abc  ww 
2 1 abc  rr 
3 2 efg  zz 
4 2 efg  xx 
5 2 efg  rr 

또 다른 해결책 :

df[['id','name']].valuesnumpy array에 열을 변환 한 후 numpy.hstack에 의해 lists의 값을 스택과 numpy.column_stack하여 추가, numpy.repeat별로 중복.

df1 = pd.DataFrame(np.column_stack((df[['id','name']].values. 
        repeat(list(map(len,df.fields)),axis=0),np.hstack(df.fields))), 
        columns=df.columns) 

print (df1) 
    id name fields 
0 1 abc  qq 
1 1 abc  ww 
2 1 abc  rr 
3 2 efg  zz 
4 2 efg  xx 
5 2 efg  rr 

보다 일반적인 솔루션은 열 fields를 필터링하고 DataFrame 생성자에 추가 항상 마지막 열 때문에 :

cols = df.columns[df.columns != 'fields'].tolist() 
print (cols) 
['id', 'name'] 

df1 = pd.DataFrame(np.column_stack((df[cols].values. 
        repeat(list(map(len,df.fields)),axis=0),np.hstack(df.fields))), 
        columns=cols + ['fields']) 

print (df1) 
    id name fields 
0 1 abc  qq 
1 1 abc  ww 
2 1 abc  rr 
3 2 efg  zz 
4 2 efg  xx 
5 2 efg  rr 
+0

감사합니다. 저는 많은 칼럼을 가지고 있으며, 미래에 어떤 칼럼이 바뀔 수도 있습니다. 내가 아는 유일한 것은 필드 열이 있다는 것입니다. 솔루션을 리팩토링 할 수있는 방법이 있습니까? 수동으로 'id', 'name'을 입력 할 필요가 없습니다. 그게 내 솔루션에서 내가 dict()를 사용하는 이유이다. – yuval

+0

그래, 나는 두 번째 해결책이 더 좋다라고 생각한다. 잠시만 기다려주세요. – jezrael

+0

그것은 효과적이고 빠릅니다. 시체에서 생성자에 대한 입력을 설명 할 수 있습니까? – yuval

2

당신의 CSV가 긴 줄의 수천의 경우, using_string_methods (아래) 있을 수 있습니다보다 빠른 using_iterrows 또는 using_repeat :

으로
csv = 'id|name|fields'+(""" 
1|abc|[qq,ww,rr] 
2|efg|[zz,xx,rr]"""*10000) 

In [210]: %timeit using_string_methods(csv) 
10 loops, best of 3: 100 ms per loop 

In [211]: %timeit using_itertuples(csv) 
10 loops, best of 3: 119 ms per loop 

In [212]: %timeit using_repeat(csv) 
10 loops, best of 3: 126 ms per loop 

In [213]: %timeit using_iterrows(csv) 
1 loop, best of 3: 1min 7s per loop 

따라서 10000 라인 CSV의 경우 using_string_methodsusing_iterrows보다 600 배 이상 빠르며 using_repeat보다 약간 빠릅니다. 데이터가 (당신이 목록을 배치하면 (예 : int64 또는 float64, 또는 문자열. 등) 기본 NumPy와의 DTYPE 경우에만


import pandas as pd 
try: from cStringIO import StringIO   # for Python2 
except ImportError: from io import StringIO # for Python3 

def using_string_methods(csv): 
    df = pd.read_csv(StringIO(csv), sep='|', dtype=None) 
    other_columns = df.columns.difference(['fields']).tolist() 
    fields = (df['fields'].str.extract(r'\[(.*)\]', expand=False) 
       .str.split(r',', expand=True)) 
    df = pd.concat([df.drop('fields', axis=1), fields], axis=1) 
    result = (pd.melt(df, id_vars=other_columns, value_name='field') 
       .drop('variable', axis=1)) 
    result = result.dropna(subset=['field']) 
    return result 


def using_iterrows(csv): 
    df = pd.read_csv(StringIO(csv), sep='|') 
    df.fields = df.fields.apply(lambda s: s[1:-1].split(',')) 
    new_df = pd.DataFrame(index=[], columns=df.columns) 

    for _, i in df.iterrows(): 
     flattened_d = [dict(i.to_dict(), fields=c) for c in i.fields] 
     new_df = new_df.append(flattened_d) 
    return new_df 

def using_repeat(csv): 
    df = pd.read_csv(StringIO(csv), sep='|') 
    df.fields = df.fields.apply(lambda s: s[1:-1].split(',')) 
    cols = df.columns[df.columns != 'fields'].tolist() 
    df1 = pd.DataFrame(np.column_stack(
     (df[cols].values.repeat(list(map(len,df.fields)),axis=0), 
     np.hstack(df.fields))), columns=cols + ['fields']) 
    return df1 

def using_itertuples(csv): 
    df = pd.read_csv(StringIO(csv), sep='|') 
    df.fields = df.fields.apply(lambda s: s[1:-1].split(',')) 
    other_columns = df.columns.difference(['fields']).tolist() 
    data = [] 
    for tup in df.itertuples(): 
     data.extend([[getattr(tup, col) for col in other_columns]+[field] 
        for field in tup.fields]) 
    return pd.DataFrame(data, columns=other_columns+['field']) 

csv = 'id|name|fields'+(""" 
1|abc|[qq,ww,rr] 
2|efg|[zz,xx,rr]"""*10000) 

일반적으로, 빠른 NumPy와/팬더 작업이 가능합니다 치명적인 NumPy dtype)을 사용하여 지그가 올라간 경우 - 은 파이썬 속도의 루프를 사용하여 목록을 처리해야합니다.

성능을 향상 시키려면 목록을 DataFrame에 배치하지 않아야합니다.

df = pd.read_csv(StringIO(csv), sep='|', dtype=None) 

과 (일반 파이썬 루프로 일반적으로 느린)에 apply 방법을 사용하지 마십시오 :

using_string_methods 문자열로 fields 데이터를로드하는 대신

df.fields = df.fields.apply(lambda s: s[1:-1].split(',')) 

을 더 빨리 사용 문자열을 개로 분리하는 벡터화 된 문자열 메서드

fields = (df['fields'].str.extract(r'\[(.*)\]', expand=False) 
      .str.split(r',', expand=True)) 

별도의 열에 필드가 있으면 pd.melt을 사용하여 DataFrame을 원하는 형식으로 바꿀 수 있습니다.

pd.melt(df, id_vars=['id', 'name'], value_name='field') 
그런데

, 당신은 약간의 수정과 함께 그것을보고 관심을 가질만한 using_iterrows 단지 빨리 using_repeat로 될 수 있습니다. 변경 내용을 using_itertuples에 표시합니다. df.itertuplesdf.iterrows보다 약간 빠른 경향이 있지만 그 차이는 미미합니다. 대부분의 속도 증가는 을 for 루프로 호출하지 않으므로 실현됩니다. leads to quadratic copying 이후입니다.

+0

감사합니다. 귀하의 접근 방식이 마음에 들지만 제 경우에는 원래 데이터가 CSV에서 나온 것이 아니므로 문제가 아닙니다. – yuval

관련 문제