반응형

Pandas DataFrame은 행과 열이 있는 데이터를 빠르게 만들고, 선택하고, 이동하고, rolling 연산을 적용하고, axis 방향으로 집계할 수 있게 해 주는 Python 데이터 분석 도구다.

이 글은 1차원 리스트와 2차원 dict를 DataFrame으로 바꾸며 컬럼과 인덱스 감각을 잡고, shift, rolling, apply, axis, transpose를 직접 실험한 메모다.

 

핵심 정리

DataFrame을 처음 쓸 때는 행과 열의 방향을 먼저 익히는 것이 중요하다. 리스트 하나를 DataFrame으로 만들면 기본 컬럼이 생기고, dict 안에 dict가 들어 있는 형태를 넣으면 바깥 키가 컬럼, 안쪽 키가 인덱스처럼 해석될 수 있다. shift는 데이터를 행 방향으로 밀어 이전 값이나 다음 값을 비교할 때 유용하고, rolling은 일정한 창 크기 안에서 합계나 사용자 정의 연산을 적용할 때 사용한다. apply는 각 컬럼이나 행에 함수를 적용하는 도구인데, axis 값에 따라 세로 방향과 가로 방향의 연산이 달라진다. transpose는 행과 열을 서로 바꿔 데이터 구조를 다시 보는 데 도움이 된다. 원문처럼 list가 element로 들어간 복잡한 구조는 깔끔해 보여도 실제 연산에서는 다루기 어려울 수 있으므로, 분석 목적에 맞게 2차원 표 형태로 풀어내는 것이 중요하다.

  • DataFrame은 행과 열이 있는 표 형태 데이터를 다루는 기본 구조다.
  • 1차원 리스트로 만들면 기본 컬럼이 자동으로 생긴다.
  • 2차원 dict는 바깥 키와 안쪽 키가 컬럼과 인덱스로 해석될 수 있다.
  • shift는 행을 앞뒤로 밀어 이전 값이나 다음 값을 비교할 때 유용하다.
  • rolling은 일정 창 크기 안에서 합계나 사용자 정의 계산을 적용한다.
  • apply는 컬럼이나 행 단위로 함수를 적용할 때 사용한다.
  • axis는 연산 방향을 정하므로 결과 모양에 직접 영향을 준다.
  • transpose는 행과 열을 바꿔 데이터 방향을 다시 볼 때 사용한다.

원문은 Pandas를 처음 써 보며 DataFrame이 루프보다 편하다고 느낀 지점을 실험한 기록입니다. 보강문에서는 생성, shift, rolling, apply, axis, transpose의 역할을 먼저 정리했습니다. DataFrame을 잘 쓰려면 개별 함수보다 데이터가 행 기준인지 열 기준인지, 연산 결과가 차원을 유지하는지 줄이는지를 계속 확인하는 습관이 중요합니다.

이어서 볼 글

 

pandas tutorial

이 링크도 좋은듯 (10min to pandas)

왜 필요하나 싶었는데 보면 볼수록 깜짝놀램...

프로토타이핑에 너무 좋은거 같고.. 같은 의미에서 python과의 궁합도 최고인듯!

z-score 만지다 보면 다차원 배열에 대해서 복잡한 연산이 필요하고 이걸 나는 자연스럽게 루프를 통해서 구현했는데..

pandas DataFrame을 보니까 그냥 함수콜 몇 번 만으로 해결되는거 보고 "아~ 이래서 쓰는구나" 싶어하는 중

1차원

접근은 [col_index][row_index]로 하네..


>>> import pandas as pd
>>> a = [1,2,3]
>>> df_a = pd.DataFrame(a)

>>> df_a[0][0]
1
>>> df_a[0][1]
2
>>> df_a[0][2]
3

1차원인데도 컬럼 0이 강제 할당되네..

2차원

2차원 dict를 넣으면 첫번째 키가 컬럼, 두번째가 row로 할당되네


>>> a = {}
>>> a['AAPL']={}
>>> a['AAPL']['0101']=11
>>> a['AAPL']['0102']=22
>>> a['NIKE']={}
>>> a['NIKE']['0101']=33
>>> a['NIKE']['0102']=44
>>> a
{'AAPL': {'0101': 11, '0102': 22}, 'NIKE': {'0101': 33, '0102': 44}}
>>> import pandas as pd
>>> df_a = pd.DataFrame(a)
>>> df_a
      AAPL  NIKE
0101    11    33
0102    22    44

element가 list인 2차원 샘플


a = {'AAPL': {'0101': [1, 2, 3], '0102': [4, 5, 6]}, 'NIKE': {'0101': [7, 8, 9], '0102': [10, 11, 12]}}

3차원

음.. 아무래도.. 3차원이상은 너무 복잡해지는 거 같다.

예를들어서 아래와 같은 데이터가 있다고 하자


>>> a = {'AAPL': {'0101': [1, 2, 3], '0102': [4, 5, 6]}, 'NIKE': {'0101': [7, 8, 9], '0102': [10, 11, 12]}}
>>> da = pd.DataFrame(a)
>>> da
           AAPL          NIKE
0101  [1, 2, 3]     [7, 8, 9]
0102  [4, 5, 6]  [10, 11, 12]

이거는 element가 list인 2차원 데이터로 볼수도 있지만 리스트 자체를 추가 차원으로 보면 3차원이다.

위와 같이 깔끔해 보이는 상태로 rolling을 하고 싶었으나 잘 되지 않았다.

그렇다면 컬럼 별로 잘라서 다시 조립하는 형태로 갈 수 밖에 없을 듯 하다.

아래같이럼 하면 맘에는 안들지만 되긴함

shift

1차원


>>> import pandas as pd
>>> a = [1,2,3]
>>> df_a = pd.DataFrame(a)
>>> df_a
   0
0  1
1  2
2  3
>>> df_a.shift(1)
     0
0  NaN
1  1.0
2  2.0
>>> df_a.shift(-1)
     0
0  2.0
1  3.0
2  NaN
>>>

2차원


>>> df_b = df_a.shift(1)
>>> df_b
      AAPL  NIKE
0101   NaN   NaN
0102  11.0  33.0

shift는 row가 밀리는 개념이네

rolling

몇 개씩 묶어서 오버랩 스타일로 연산하고 결과를 쓰는 걸 할때..


>>> df_a
   0
0  1
1  2
2  3
>>> df_a.rolling(2).sum()
     0
0  NaN
1  3.0
2  5.0

0 - 1 인덱스에 있는 값 두 개 묶어서 1인덱스에 쓰고 (1 + 2 = 3)

1 - 2 인덱스에 있는 값 두 개 묶어서 2인덱스에 쓰고 (2 + 3 = 5)

하는 걸 볼 수 있다.

apply lambda

sum()같은 거 대신에 개별 element에 대한 연산을 apply를 써서 정의해 줄 수 있다.

아래처럼 하면 그냥 sum()하고 같은 거고


>>> df_a.rolling(2).apply(lambda x: sum(x))
     0
0  NaN
1  3.0
2  5.0

아래처럼 개별 element에 대한 연산을 포함시킬수도 있음


>>> df_a.rolling(2).apply(lambda x: sum(x+1)+1)
     0
0  NaN
1  6.0
2  8.0

아래처럼 한 번 더 연산을 적용해서 합칠수도 있음(mean()은 pandas 함수)


>>> df_a.rolling(2).apply(lambda x: sum(x+1)+1).apply(lambda x: x.mean())
0    7.0
dtype: float64

아래처럼 x라는 표현을 살리면 계산식에 x.sum()같은 aggregation 함수가 들어가도 차원이 축소하지 않고


>>> a = [1,2,3]
>>> df_a = pd.DataFrame(a)
>>> df_a
   0
0  1
1  2
2  3
>>> df_a.apply(lambda x: x + 1)
   0
0  2
1  3
2  4
>>> df_a.apply(lambda x: x + x.sum())
   0
0  7
1  8
2  9

아래처럼 x라는 표현을 없애고 aggregation 함수만 놓으면 차원이 축소한다. (상수식을 넣어도 축소한다)


>>> df_a
   0
0  1
1  2
2  3
>>> df_a.apply(lambda x: x.sum())
0    6
dtype: int64

2차원인 경우는 아래처럼 aggregation의 방향을 바꿀 수 있다(0이면 세로축으로 row간에 연산해주고, 1이면 가로축으로 col간 연산해줌)


>>> a = {'AAPL': {'0101': 11, '0103': 33, '0102': 22}, 'NIKE': {'0101': 33, '0103': 55, '0102': 44}}
>>> df_a = pd.DataFrame(a)
>>> df_a
      AAPL  NIKE
0101    11    33
0102    22    44
0103    33    55
>>> df_a.apply(lambda x: x/x-1+x.sum())
      AAPL   NIKE
0101  66.0  132.0
0102  66.0  132.0
0103  66.0  132.0
>>> df_a.apply(lambda x: x/x-1+x.sum(),axis=1)
      AAPL  NIKE
0101  44.0  44.0
0102  66.0  66.0
0103  88.0  88.0
>>>

(위에서 x/x-1 이라는 표현은 0이라고 쓰면 aggregation()되어 버려서 그걸 막기위해 x로 0을 표현한것임)

포맷팅도 가능한것 같다.


.apply('{:03.0f}'.format)

http://stackoverflow.com/questions/38473205/pandas-rolling-computations-for-printing-elements-in-the-window

transpose (row, col 교체.. 옆으로 눞히기)


>>> df_a = pd.DataFrame(a)
>>> df_a
      AAPL  NIKE
0101    11    33
0102    22    44
0103    33    55
>>> df_a.transpose()
      0101  0102  0103
AAPL    11    22    33
NIKE    33    44    55
>>>

와 이것도 진짜 대박이다 @.@

sort

dict를 dataframe으로 바꿀때 기본적으로 row방향에 대해서 소트 해주는 것 같다.

• 흠.. 이거에 대해서 웹문서 찾아보는데 쉽지는 않네 ㅠ.ㅠ

*

column 방향으로는 소트해 주지 않는 것 같다.

tranpose()하면 다시 바뀐 row방향에 대해서(이전에 col방향이었던)또 소팅해서 보여주는 듯

여기 좋다.

여기서는 뭔가 심오한 토론들이

일부만 잘라서(보기, 로그찍기 등)

첫번째 컬럼으로 세 줄만 보고싶으면 다음처럼 하면 된다.


>>> a[a.columns[0]].head(3)
2015-06-10    [10250.0, 10300.0, 10283.3333333, 10266.666666...
2015-06-11    [10250.0, 10400.0, 10383.3333333, 10400.0, 103...
2015-06-12    [10183.3333333, 10150.0, 10100.0, 10066.666666...
Name: KR7000030007, dtype: object
>>>

컬럼을 2개이상 같이 보고 싶으면 좀 복잡한 것 같다. (seeborn으론 쉽다?)

element가 list일때 rolling이 안됨


>>> c
1    [1, 2, 3]
2    [2, 3, 4]
3    [3, 4, 5]

위처럼 element가 숫자가 아니라 list이면


>>> c.rolling(2).sum()
1    [1, 2, 3]
2    [2, 3, 4]
3    [3, 4, 5]

위처럼 rolling을 시도했을때 no action이 되어 버린다.


>>> c.apply(lambda x: pd.Series(x))
   0  1  2
1  1  2  3
2  2  3  4
3  3  4  5

이때는 위처럼 Series로 바꿔주면 되더라

다시 list로 합치려면 다음처럼 하면 된다.


>>> d['haha']=c.values.tolist()
>>> d
   0  1  2       haha
1  1  2  3  [1, 2, 3]
2  2  3  4  [2, 3, 4]
3  3  4  5  [3, 4, 5]
>>> e = d['haha']
>>> e
1    [1, 2, 3]
2    [2, 3, 4]
3    [3, 4, 5]
Name: haha, dtype: object
>>>

여기서 개인적인 생각으로는 d['haha'] = c.values.tolist() 이게 명확한 문법같지는 않다.

왜냐면 row 단위로 적용된다는건데.. 그 의미가 표면상에 나와있는 것 같지가 않기 때문

또한 위처럼 d, e 두 단계로 하지 않고 한단계로 할 수 있는 방법도 궁금한데.. 그냥 아래처럼 해봤을때는 컬럼별로 자동 분리가 되어 버렸다.


>>> f = pd.DataFrame(c.values.tolist())
>>> f
   0  1  2
0  1  2  3
1  2  3  4
2  3  4  5

element 가 list일때 다루기

복잡해서 별도 세션으로 뺌 ㅠ.ㅠ

sum element column 추가


a = {'SWC': {'0101': [10, 20, 30], '0102': [40, 50, 60], '0103': [70,80,90]}, 
     'AAPL': {'0101': [1, 2, 3], '0102': [4, 5, 6], '0103': [7,8,9]}, 
     'NIKE': {'0101': [7, 8, 9], '0102': [10, 11, 12], '0103':[13,14,15]}}
df_a = DataFrame(a)

아래 처럼 꽤나 삽질해야했다.

코드가 깔끔하지도 않은 것 같고 ㅠ

z-score 계산

어려울거 같았는데 하다보니까 또 되네 ㅎㅎ

특정 row 지우기

쉬운 방법1. python slicing

그냥 파이선에서 쓰듯이 잘라쓰면된다.

예를 들어 아래처럼 하면 첫줄 지울 수 있듯이


>>> a = [11,22,33]
>>> a[1:]
[22, 33]
>>>

아래처럼 같은 방법으로 첫 줄을 지울 수 있다.


>>> a = {'AAPL': {'0101': [1, 2, 3], '0102': [4, 5, 6]}, 'NIKE': {'0101': [7, 8, 9], '0102': [10, 11, 12]}}
>>> a_df = DataFrame(a)
>>> a_df
           AAPL          NIKE
0101  [1, 2, 3]     [7, 8, 9]
0102  [4, 5, 6]  [10, 11, 12]
>>> a_df[1:]
           AAPL          NIKE
0102  [4, 5, 6]  [10, 11, 12]

근데 in-place는 아니고 다른 변수에 대입해야 효과가 나는건데.. 다음처럼 drop과 index라는걸 쓰면 다른 변수에 대입하지 않고도 inplace로 지울 수 있다.


>>> a_df
           AAPL          NIKE
0101  [1, 2, 3]     [7, 8, 9]
0102  [4, 5, 6]  [10, 11, 12]
>>> a_df.drop(a_df.index[0:1], inplace=True)

>>> a_df
           AAPL          NIKE
0102  [4, 5, 6]  [10, 11, 12]

iteration

컬럼 major order로 할때는 dict와 동일한 방식으로 처리 가능

row major order로 할때는 iterrows()를 사용하면 가능

integer index 관련

전체 개수는 len(df)로 파악 가능

각 줄은 df.iloc[x] 로 접근 가능

name index를 알 때 다음 방법으로 integer index에 접근 가능


df.index.get_loc('name_index')

Series

series[true-false series]하면 true인것만 모인 series가 되는 것 같다.

반응형

+ Recent posts