티스토리 뷰

Back-ends/pandas

Pandas

이주성 2020. 8. 28. 18:12
pandas

Table of Contents

  1. 파일 읽기/쓰기
  2. DataFrame 읽기
  3. DataFrame Query
  4. DataFrame 수정
  5. Merge and Join
  6. 기타

파일 읽기

CSV 파일 읽기/쓰기

$ df = pd.read_csv('./app_unique_20200820.csv', usecols=[0, 1], names=['timestamp', 'stb_id'], dtype=str)
...
INDEX  timestamp               stb_id
0      2019-11-12 10:53:48.415 {27574A07-25AA-11E8-AD34-XXXXXXXXXXXX}
1      2019-11-12 11:40:02.479 {1EA317EC-9647-11E9-A51A-XXXXXXXXXXXX}
2      2019-11-12 11:40:43.811 {517D6034-97B0-11E9-A51A-XXXXXXXXXXXX}
3      2019-11-12 14:55:00.229 {440A58F7-6A4F-11E9-B4BF-XXXXXXXXXXXX}
4      2019-11-12 15:39:08.287 {6972F28F-3FB2-11E9-B4BF-XXXXXXXXXXXX}

$ df.to_csv('app-stbid-new.csv')

timestamp 읽기/쓰기

timestamp를 읽으려면 parse_dates 를 써야 한다. 근데, 표준 포멧(ex. 2020-01-01 12:01:01)이 아니면, 이것만으로 안된다. 이럴때는 infer_datetime_format 를 써야 한다. 이 옵션은 대표적인 date포멧(20210524155933)을 자동으로 유추해서 파싱해준다. 만약 month보다 day가 먼저 오면 유추시ㅡ dayFirst=True 를 써야 한다.

$ df = pd.read_csv('/unique.csv', parse_dates=[0], usecols=[0, 1], names=['timestamp', 'pcid'])
.... CSV 파일 ...
2020-01-01 12:01:01, XXXX
2020-01-01 12:02:01, YYYY

$ df = pd.read_csv('/unique.csv', parse_dates=[0], infer_datetime_format=True, usecols=[0, 1], names=['timestamp', 'pcid'])
... CSV 파일 ...
20210524155933, XXXX
20210524155933, YYYY

SQL 파일 읽기/쓰기

$ conn = sqlite3.connect('../app_stbid.sqlite3')
$ df = pd.read_sql('SELECT stb_id FROM app_stbid', con=conn)
INDEX  stb_id
0      {27574A07-25AA-11E8-AD34-21E42E7495CA}
1      {1EA317EC-9647-11E9-A51A-61AC1B87CDDD} 

$ df.to_sql('../app_stbid_out.sqlite3', conn, if_exists='replace')
$ conn.commit()
$ conn.close()

DataFrame 읽기

읽기 Basic

$ df.head()
INDEX  stb_id
0      2019-11-12 10:53:48.415 {27574A07-25AA-11E8-AD34-XXXXXXXXXXXX}
1      2019-11-12 11:40:02.479 {1EA317EC-9647-11E9-A51A-XXXXXXXXXXXX}
2      2019-11-12 11:40:43.811 {517D6034-97B0-11E9-A51A-XXXXXXXXXXXX}
3      2019-11-12 14:55:00.229 {440A58F7-6A4F-11E9-B4BF-XXXXXXXXXXXX}
4      2019-11-12 15:39:08.287 {6972F28F-3FB2-11E9-B4BF-XXXXXXXXXXXX}

$ df.tail(2)
INDEX  stb_id
0      2019-11-12 10:53:48.415 {27574A07-25AA-11E8-AD34-XXXXXXXXXXXX}
1      2019-11-12 11:40:02.479 {1EA317EC-9647-11E9-A51A-XXXXXXXXXXXX}

$ df.shape 
(40000, 2)  # (ROW갯수, 필드갯수)

$ df.info() 
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 414079 entries, 0 to 414078
Data columns (total 2 columns):
timestamp    414079 non-null object
stb_id       414078 non-null object
dtypes: object(2)
memory usage: 6.3+ MB

$ df['stb_id'].describe()
unique                                      5000
top       {735EBB4E-3A7A-11E9-8A0F-XXXXXXXXXXXX}
freq                                        1000
Name: stb_id, dtype: object

$ df.dtypes
timestamp    object
stb_id       object
dtype: object

DataFrame Query

Query Basic

$ purchase_1 = pd.Series({'Name': 'Chris','Item Purchased': 'Dog Food', 'Cost': 22.50})
$ purchase_2 = pd.Series({'Name': 'Kevyn','Item Purchased': 'Kitty Litter','Cost': 2.50})
$ purchase_3 = pd.Series({'Name': 'Vinod','Item Purchased': 'Bird Seed','Cost': 5.00})
$ df = pd.DataFrame([purchase_1, purchase_2, purchase_3], index=['Store 1', 'Store 1', 'Store 2'])
$ df
INDEX   Cost    Item Purchased  Name
Store_1 22.5    Dog Food        Chris
Store_1 2.5     Kitty Litter    Kevyn
Store_2 5.0     Bird Seed       Vinod

$ df.loc['Store_1'] # df['Store_1']과 동일
INDEX   Cost    Item Purchased  Name
Store_1 22.5    Dog Food        Chris
Store_1 2.5     Kitty Litter    Kevyn

$ df.loc['Store_1', 'Cost'] # df.loc['Store_1']['Cost']과 동일
Store 1    22.5
Store 1     2.5
Name: Cost, dtype: float64

Query

df['_merge']의 값중에 _left_only 인 ROW만 추출할려면 아래처럼 해라.

$ df_left = df.loc[df['_merge'] == 'left_only'] # df[df['_merge'] == 'left_only'] 와 동일
INDEX  timestamp               stb_id                                  _merge
0      2019-11-12 10:53:48.415 {27574A07-25AA-11E8-AD34-XXXXXXXXXXXX}  left_only
1      2019-11-12 11:40:02.479 {1EA317EC-9647-11E9-A51A-XXXXXXXXXXXX}  left_only

$ df_left = df.loc[df['_merge'] == 'left_only', 'right_only', ['timestamp', '_merge']]
INDEX  timestamp               _merge              
0      2019-11-12 10:53:48.415 left_only
1      2019-11-12 11:40:02.479 left_only
2      2019-11-12 11:40:43.811 right_only

for loop

dataframe를 가지고 for문을 돌리는것은 부하가 큰 작업이다( 수천만 ROW일때를 상상해봐라 ). 그래서, pandas에서는 아래와 같이 List Comprehension 을 쓰라고 한다 ( df.iterrows() 같은건 쓰지 마라 )

def func(arg):
    print(arg)
# List Comprehension
data = [func(arg) for arg in df['stb_id']]
data2 = [func(args for args in zip(df['timestamp'], df['stb_id'])]

DataFrame 수정

컬럼 수정

CD라는 컬럼에 D 라는 스트링을과 LIST를 넣어보자. 중요한점은 컬럼에 LIST를 넣으려면, df와 LIST의 size가 동일해야 한다.

$ df['CD'] = 'D'
# df['CD2']와 list의 size가 동일해야 한다.
$ list = [str(x) for x in range(100)]
$ df['CD2'] = list

timestamp필드에 _merge필드값을 붙여보자( 의미없는 작업이긴 하지만.. )

$ df['timestamp'] = df['timestamp'] + df['_merge']

INDEX  timestamp                         _merge              
0      2019-11-12 10:53:48.415_left_only left_only
1      2019-11-12 11:40:02.479_left_only left_only
2      2019-11-12 11:40:43.811_left_only right_only

컬럼 삭제

$ df = df.drop(columns=['timestamp'])
$ df.head()
INDEX  stb_id
0      {27574A07-25AA-11E8-AD34-XXXXXXXXXXXX}
1      {1EA317EC-9647-11E9-A51A-XXXXXXXXXXXX}
2      {517D6034-97B0-11E9-A51A-XXXXXXXXXXXX}
3      {440A58F7-6A4F-11E9-B4BF-XXXXXXXXXXXX}
4      {6972F28F-3FB2-11E9-B4BF-XXXXXXXXXXXX}

Merge and Join

Merge Basic

집합의 종류는 아래표같이 4종류가 있다. how 아규먼트에 아래값을 설정하면 두 dataframe을 merge해준다.

how 설명
outer 합집합
inner 교집합
left 왼쪽 집합 (교집합 포함)
right 오른쪽 집합 (교집합 포함)

left_index, left_on, right_on 같은 아규먼트를 설정하지 않으면, 기본적으로 이름이 같은 컬럼을 기준으로 merge를 해준다.

$ df_join = df_old.merge(df_new, how='outer', indicator=True)
$ df_join
INDEX  stb_id                                  _merge
0      {27574A07-25AA-11E8-AD34-XXXXXXXXXXXX}  left_only
1      {1EA317EC-9647-11E9-A51A-XXXXXXXXXXXX}  left_only
2      {517D6034-97B0-11E9-A51A-XXXXXXXXXXXX}  both
3      {440A58F7-6A4F-11E9-B4BF-XXXXXXXXXXXX}  left_only
4      {6972F28F-3FB2-11E9-B4BF-XXXXXXXXXXXX}  both
... ... ...
533526  {81154ABC-10C7-11E4-B6D2-XXXXXXXXXXXX}  right_only
533527  {C0EAA9CF-F826-11E8-8A0F-XXXXXXXXXXXX}  right_only
533528  {B6AA7C54-E1F3-11EA-B93B-XXXXXXXXXXXX}  right_only
533529  {4C0B3964-CC3B-11E7-A47A-XXXXXXXXXXXX}  right_only
533530  {C3C64036-0CBA-11E8-A47A-XXXXXXXXXXXX}  right_only

Merge 고급

INDEX값을 기준으로 merge를 할꺼면 left_index=True, right_index=True 을 쓰면 된다.

$ staff_df
Name(INDEX)  Role Name                 
Kelly        Director of HR
Sally        Course liasion
James        Grader
$ student_df
Name(INDEX)  School Name              
James        Business
Mike         Law
Sally        Engineering

$ pd.merge(staff_df, student_df, how='outer', left_index=True, right_index=True)
Name(INDEX)   Role    School
James         Grader  Business
Kelly         Director of HR  NaN
Mike          NaN Law
Sally         Course liasion  Engineering

아니면, 특정 컬럼을 기준으로 merge를 하려면 어떻할까? left_on, right_on 에 기준 컬럼명을 적어주면 된다. 물론 left_index=True와 right_on을 혼용 가능하다

$ staff_df = staff_df.reset_index()
$ student_df = student_df.reset_index()
$ pd.merge(staff_df, student_df, how='left', left_on='Name', right_on='Name')
INDEX    Name    Role    School
0        Kelly   Director of HR  NaN
1        Sally   Course liasion  Engineering
2        James   Grader  Business

기타

apply, map

반복작업이 있을때 apply를 써라(python의 map가 동일). List Comprehension방법이 있긴 하지만, 이게 더 pandas를 공식지원하고 더 빠른 방법이다.

import numpy as np
$ df
INDEX0 INDEX1   SUMLEV  CENSUS2010POP   Estimates Base 2010 POPESTIMATE2010 POPESTIMATE2011 POPESTIMATE2012 ... RDOMESTICMIG2011    RDOMESTICMIG2012
Alabama Autauga 50.0    54571.0         54571.0             54660.0         55253.0         55175.0 ...         7.242091            -2.915927          
Baldwin County  50.0    182265.0        182265.0            183193.0        186659.0        190396.0...         14.832960           17.647293 
Barbour County  50.0    27457.0         27457.0             27341.0         27226.0         27159.0 ...         -4.728132           -2.500690          

def min_max(row):
    data = row[['POPESTIMATE2010',
                'POPESTIMATE2011',
                'POPESTIMATE2012',
                'POPESTIMATE2013',
                'POPESTIMATE2014',
                'POPESTIMATE2015']]
    row['max'] = np.max(data)
    row['min'] = np.min(data)
    return row
$ df.apply(min_max, axis=1) # axis=1은 ROW단위 반복처

INDEX0 INDEX1   max      min        SUMLEV  CENSUS2010POP   Estimates Base 2010 POPESTIMATE2010 POPESTIMATE2011 POPESTIMATE2012 ... RDOMESTICMIG2011    RDOMESTICMIG2012    
Alabama Autauga 55347.0  54660.0    50.0    54571.0         54571.0             54660.0         55253.0         55175.0 ...         7.242091 
Baldwin County  203709.0 183193.0   50.0    182265.0        182265.0            183193.0        186659.0        190396.0...         14.832960
Barbour County  27341.0  26489.0    50.0    27457.0         27457.0             27341.0         27226.0         27159.0 ...         -4.728132

agg

// 무식한 방법
$ for group, frame in df.groupby('STNAME'):
    avg = np.average(frame['CENSUS2010POP'])
...
Counties in state Alabama have an average population of 71339.3432836
Counties in state Alaska have an average population of 24490.7241379

// agg를 쓴 유식한 방법
$ df.groupby('STNAME').agg({'CENSUS2010POP': np.average})
...
STNAME(INDEX)   CENSUS2010POP
Alabama         71339.343284
Alaska          24490.724138
Arizona         426134.466667
Arkansas        38878.906667

실행속도 테스트

파이선코드가 실행시간이 얼마나 걸리나 측정하고 싶을때가 있다. 이럴때 jupyter-notebook이 좋은 대안이다. %%timeit -n 1000을 쓰면, 아래의 코드를 1000번 반복해 주고, 걸리는 시간을 알려준다. 아래코드는 for loop 으로 series sum을 구할때 실행시간 테스트이다. 2.18ms per loop 이 걸렸다. 역시 for loop은 오래걸린다 .

$ s = pd.Series(np.random.randint(0,1000,10000))

%%timeit -n 1000
summary = 0
for item in s:
    summary+=item

2.18 ms ± 103 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 

pandas에서 추천하는 vector방법을 쓰면, 256 µs per loop 이 걸린다. 역시 엄청 빠르다

%%timeit -n 1000
summary = np.sum(s)

256 µs ± 40.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함