Table of Contents
파일 읽기
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)