이번 포스팅에서는 Python pandas library를 이용하여 시계열 데이터(time series data)를 10분, 20분, 1시간, 1일, 1달 등의 특정 시간 단위(time span) 구간별로 집계/요약 하는 방법을 소개하겠습니다. (Downsampling)


(* PostgreSQL, Greenplum database로 특정 시간 단위 구간별 시계열 데이터 집계, 요약하는 방법은  https://rfriend.tistory.com/495 참조)


이전에 소개했었던 groupby() operator를 사용해서 그룹별로 집계/요약하는 방법을 사용할 수도 있는데요, 시계열 데이터의 경우 pandas의 resample() method를 사용하면 좀더 편리하고 코드도 깔끔하게 시간 단위 구간별로 시계열 데이터를 집계/요약할 수 있습니다. 





먼저 '년-월-시간:분:초'로 이루어진 time-stamp 를 index로 가지고, 가격(price)와 수량(amount) 의 두 개의 칼럼을 가지는 간단한 시계열 데이터를 만들어보겠습니다. pandas의 date_range(from, to, freq) 함수를 해서 '2분 간격(freq='2min')의 date range 데이터를 만들었습니다. 이 중에서 20개 행만 선택해서 예를 들어보겠습니다. 



import pandas as pd

import numpy as np


# generate time series index

range = pd.date_range('2019-12-19', '2019-12-20', freq='2min')

df = pd.DataFrame(index = range)[:20]


# add 'price' columm using random number

np.random.seed(seed=1004) # for reproducibility

df['price'] = np.random.randint(low=10, high=100, size=20)


# add 'amount' column unsing random number

df['amount'] = np.random.randint(low=1, high=5, size=20)


print('Shape of df DataFrame:', df.shape)

[Out]:Shape of df DataFrame: (20, 2)


df

[Out]:

priceamount
2019-12-19 00:00:00124
2019-12-19 00:02:00212
2019-12-19 00:04:00411
2019-12-19 00:06:00794
2019-12-19 00:08:00612
2019-12-19 00:10:00811
2019-12-19 00:12:00243
2019-12-19 00:14:00621
2019-12-19 00:16:00763
2019-12-19 00:18:00631
2019-12-19 00:20:00952
2019-12-19 00:22:00821
2019-12-19 00:24:00823
2019-12-19 00:26:00701
2019-12-19 00:28:00304
2019-12-19 00:30:00331
2019-12-19 00:32:00222
2019-12-19 00:34:00773
2019-12-19 00:36:00583
2019-12-19 00:38:00963

 




  (1) 10분 단위 구간별로 각 칼럼의 첫번째 값(first value), 마지막 값(last value) 구하기

      (select the first and last value by 10 minutes time span using pandas resample method)


resample('10T') 는 '년-월-일 시간:분:초' 의 시계열 index를 10분 단위의 동일 간격별로 데이터를 뽑으라는 뜻입니다. pandas의 groupby() 에서 split-apply-combine에서 동일 시간대 간격으로 split 의 역할을 한다고 생각할 수 있습니다


[ resample() 메소드의 시간 단위 구간 설정 ]

- 5분 단위 구간    : resample('5T')

- 10분 단위 구간  : resample('10T')

- 20분 단위 구간 : resample('20T')

- 1시간 단위 구간 : resample('1H')

- 1일 단위 구간    : resample('1D')

- 1주일 단위 구각 : resample('1W')

- 1달 단위 구간    : resample('1M')

- 1년 단위 구간    : resample('1Y')


각 시간 단위 구간(time span) 별로 시간 순서대로 정렬된 상태에서 첫번째 행의 값(first row's value)은 first() 메소드를 사용하며, 마지막 행의 값(last row's value)은 last() 메소드를 사용해서 구할 수 있습니다. (groupby 의 split-apply-combine 중에서 apply 에 해당한다고 생각할 수 있습니다)


# Resampling by a given time span (group)

# : first, last

df_summary = pd.DataFrame()


df_summary['price_10m_first'] = df.price.resample('10T').first()

df_summary['price_10m_last'] = df.price.resample('10T').last()

df_summary['amount_10m_first'] = df.amount.resample('10T').first()

df_summary['amount_10m_last'] = df.amount.resample('10T').last()


df_summary

price_10m_firstprice_10m_lastamount_10m_firstamount_10m_last
2019-12-19 00:00:00126142
2019-12-19 00:10:00816311
2019-12-19 00:20:00953024
2019-12-19 00:30:00339613

 




  (2) 10분 단위 구간별로 숫자형 데이터의 합계, 누적 합계 구하기

     (sum, cumulative sum by 10 minutes time span using pandas resample method)



# Resampling by a given time span (group)

# sum, cumulative sum

df_summary = pd.DataFrame()


df_summary['price_10m_sum'] = df.price.resample('10T').sum()

df_summary['price_10m_cumsum'] = df.price.resample('10T').sum().cumsum()

df_summary['amount_10m_sum'] = df.amount.resample('10T').sum()

df_summary['amount_10m_cumsum'] = df.amount.resample('10T').sum().cumsum()


df_summary

price_10m_sumprice_10m_cumsumamount_10m_sumamount_10m_cumsum
2019-12-19 00:00:002142141313
2019-12-19 00:10:00306520922
2019-12-19 00:20:003598791133
2019-12-19 00:30:0028611651245

 




  (3) 10분 단위 구간별로 최소값, 최대값, 평균, 중앙값, 범위 구하기

   (summary statistics by 10 minutes time span using pandas resample method)


최소값(min), 최대값(max), 평균(mean), 중앙값(median) 요약통계량은 min(), max(), mean(), median() 메소드를 이용하여 구할 수 있으며, 범위(range)는 해당 메소드가 없어서 범위(range) = 최대값(max) - 최소값(min) 의 계산을 해서 구하였습니다. 



# Resampling by a given time span (group)

# min, max, mean, median, range

df_summary = pd.DataFrame()


df_summary['price_10m_min'] = df.price.resample('10T').min()

df_summary['price_10m_max'] = df.price.resample('10T').max()

df_summary['price_10m_mean'] = df.price.resample('10T').mean()

df_summary['price_10m_median'] = df.price.resample('10T').median()

df_summary['price_10m_range'] = \

    df.price.resample('10T').max() - df.price.resample('10T').min()


df_summary

price_10m_minprice_10m_maxprice_10m_meanprice_10m_medianprice_10m_range
2019-12-19 00:00:00127942.84167
2019-12-19 00:10:00248161.26357
2019-12-19 00:20:00309571.88265
2019-12-19 00:30:00229657.25874




  (4) 10분 단위 구간별로 분산(variance), 표준편차(standard deviation) 구하기

    (variance, standard deviation by 10 minutes time span using pandas resample(() method)


resample('10T') 로 10분 단위 구간별로 데이터를 그룹으로 뽑고, var() 메소드로 표본 분산(sample variance)을 구합니다. (* 참고: 모집단 분산(population variance)이 편차 제곱의 합을 원소의 개수 N으로 나누어주는 반면에, 표본 분산(sample variance)는 편차 제곱의 합을 원소의 개수에서 1개를 뺀 N-1로 나누어준다는 차이점이 있습니다)


표본 표분편차(sample standard deviation)을 직접 구할 수 있는 메소드가 없어서 표본 분산에 제곱근(square root)을 취하여 표본 표준편차를 구하였습니다. 



# Resampling by a given time span (group)

# variance, standard deviation

df_summary = pd.DataFrame()


# sample variance 1/(N-1)*sigma(X-X_bar)^2

df_summary['price_10m_var'] = df.price.resample('10T').var() 


# sample standard deviation using sqrt(var) formula

df_summary['price_10m_stddev'] = np.sqrt(df.price.resample('10T').var())

price_10m_varprice_10m_stddev
2019-12-19 00:00:00767.227.698375
2019-12-19 00:10:00499.722.353971
2019-12-19 00:20:00624.224.983995
2019-12-19 00:30:00930.730.507376




  (5) 특정 시간 단위 구간별로 요약 통계량 구하는 사용자 정의 함수

     (User Defined Function for aggregating summary statistics by specific time span)


위의 (1) ~ (4)번에서 pandas의 resample() 메소드를 사용하여 시계열 데이터를 특정 시간 단위 구간별로 샘플링하고, 첫번째 값(first), 마지막 값(last), 합(sum), 누적합(cumsum), 최소값(min), 최대값(max), 평균(mean), 중앙값(median), 구간(range), 분산(variance), 표준편차(standard deviation) 을 구하는 방법을 소개하였습니다. 


이를 좀더 사용하기 편리하도록 아래의 매개변수를 인자로 가지는 사용자 정의 함수를 정의해보겠습니다. 


[ resample_summary() 사용자 정의 함수 매개변수 ] 


  (a) ts_data : '년-월-일 시간:분:초'의 시계열 범위 데이터를 index로 가지는 시계열 데이터 DataFrame

  (b) col_nm : 집계/요약의 대상이 되는 칼럼 이름

  (c) time_span : 특정 시간 단위 구간 (예: 10분 단위 '10T', 1시간 단위 '1H', 1일 단위 '1D' 등)

  (d) func_list : 집계/요약할 함수 (예: 첫번째 값 'first', 마지막 값 'last', 합 'sum', 누적합 'cumsum', 최소값 'min', 최대값 'max', 평균 'mean', 중앙값 'median', 범위 'range', 표본 분산 'var', 표본 표준편차 'stddev' 등)


공통으로 사용되는 부분인 resampler = ts_data[col_nm].resample(time_span) 를 resampler 객체로 만들어서 반복해서 사용하였습니다. 


그리고 사용자가 입력(선택)한 집계/요약 함수만 집계/요약하여 반환하도록 if [function name] in func_list 조건문을 추가해주었습니다. 


집계/요약된 값의 칼럼 이름은 이해하기 쉽도록 접미사(suffix)를 붙어서 [ 기존 칼럼 이름 + '_' + 시간 단위 구간 + '_' + 집계/요약함수 ] 를 이어붙여서 새로 만들어주었습니다. (예: price_10T_first)



# UDF of Resampling by column name, time span and summary functions 

def resample_summary(ts_data, col_nm, time_span, func_list):

    

    import numpy as np

    import pandas as pd

    

    df_summary = pd.DataFrame() # blank DataFrame to store results

    

    # resampler with column name by time span (group by)

    resampler = ts_data[col_nm].resample(time_span)

    

    # aggregation functions with suffix name

    if 'first' in func_list:

        df_summary[col_nm + '_' + time_span + '_first'] = resampler.first()

    

    if 'last' in func_list:

        df_summary[col_nm + '_' + time_span + '_last'] = resampler.last()

        

    if 'sum' in func_list:

        df_summary[col_nm + '_' + time_span + '_sum'] = resampler.sum()

        

    if 'cumsum' in func_list:

        df_summary[col_nm + '_' + time_span + '_cumsum'] = resampler.sum().cumsum()

        

    if 'min' in func_list:

        df_summary[col_nm + '_' + time_span + '_min'] = resampler.min()

        

    if 'max' in func_list:

        df_summary[col_nm + '_' + time_span + '_max'] = resampler.max()

    

    if 'mean' in func_list:

        df_summary[col_nm + '_' + time_span + '_mean'] = resampler.mean()

        

    if 'median' in func_list:

        df_summary[col_nm + '_' + time_span + '_median'] = resampler.median()

        

    if 'range' in func_list:

        df_summary[col_nm + '_' + time_span + '_range'] = resampler.max() - resampler.min()

        

    if 'var' in func_list:

        df_summary[col_nm + '_' + time_span + '_var'] = resampler.var() # sample variance

        

    if 'stddev' in func_list:

        df_summary[col_nm + '_' + time_span + '_stddev'] = np.sqrt(resampler.var())

    

    return df_summary

 



위의 (5)번에서 정의한 resample_summary() 사용자 정의 함수를 이용하여, df 데이터셋의 'price' 칼럼에 대해 '10분 단위 구간별로(time_span = '10T') 첫번째 값('first'), 마지막 값('last'), 합('sum'), 누적합('cumsum'), 최소값('min'), 최대값('max') 을 구해보겠습니다. 



func_list = ['first', 'last', 'sum', 'cumsum', 'min', 'max']


resample_summary(df, 'price', '10T', func_list)

price_10T_firstprice_10T_lastprice_10T_sumprice_10T_cumsumprice_10T_minprice_10T_max
2019-12-19 00:00:0012612142141279
2019-12-19 00:10:0081633065202481
2019-12-19 00:20:0095303598793095
2019-12-19 00:30:00339628611652296

 



이번에는 시간 단위 구간을 '20분 ('20T')'으로 늘려서 resample_summary() 사용자 정의 함수를 사용해 보겠습니다. 



func_list = ['mean', 'median', 'range', 'var', 'stddev']


resample_summary(df, 'price', '20T', func_list)

price_20T_meanprice_20T_medianprice_20T_rangeprice_20T_varprice_20T_stddev
2019-12-19 00:00:0052.061.569657.11111125.634179
2019-12-19 00:20:0064.573.574750.27777827.391199

 



이번에는 집계/요약의 대상이 되는 칼럼을 '수량(amount)' 으로 바꾸어서 resample_summary() 사용자 정의 함수를 사용해 보겠습니다. 



func_list = ['mean', 'median', 'range', 'var', 'stddev']


resample_summary(df, 'amount', '20T', func_list) # with 'amount' column

amount_20T_meanamount_20T_medianamount_20T_rangeamount_20T_varamount_20T_stddev
2019-12-19 00:00:002.22.031.5111111.229273
2019-12-19 00:20:002.32.531.1222221.059350

 



집계/요약할 함수를 평균('mean'), 중앙값('median'), 범위('range'), 분산('var'), 표준편차('stddev')로 바꾸어서 resample_summary() 사용자 정의 함수를 사용해 보겠습니다. 



func_list = ['mean', 'median', 'range', 'var', 'stddev']


resample_summary(df, 'price', '10T', func_list)

price_10T_meanprice_10T_medianprice_10T_rangeprice_10T_varprice_10T_stddev
2019-12-19 00:00:0042.84167767.227.698375
2019-12-19 00:10:0061.26357499.722.353971
2019-12-19 00:20:0071.88265624.224.983995
2019-12-19 00:30:0057.25874930.730.507376

 




이번에는 데이터를 2019-12-19 일에서 2020-01-18일 까지 약 한달 간의 시계열 데이터를 난수로 생성해서 ==> 시간 단위 구간을  1시간('1H'), 1일('1D'), 1주('1W'), 1달('1M') 로 바꾸어가면서 집계/요약을 해보겠습니다. 



# generate time series index

range = pd.date_range('2019-12-19', '2020-01-18', freq='2min') # one month period

df_1m = pd.DataFrame(index = range)


# add 'price' columm using random number

np.random.seed(seed=1004) # for reproducibility

df_1m['price'] = np.random.randint(low=10, high=100, size=len(df))


# add 'amount' column unsing random number

df_1m['amount'] = np.random.randint(low=1, high=5, size=len(df))


print('Shape of df_1m DataFrame:', df_1m.shape)

Shape of df_1m DataFrame: (21601, 2)

# by 1 Hour

func_list = ['first', 'sum', 'mean', 'stddev']
resample_summary(df_1m, 'price', '1H', func_list).head() # by 1 Hour
price_1H_firstprice_1H_sumprice_1H_meanprice_1H_stddev
2019-12-19 00:00:0012168456.13333325.143359
2019-12-19 01:00:0044153451.13333324.764732
2019-12-19 02:00:0070143547.83333325.223256
2019-12-19 03:00:0022186762.23333324.842515
2019-12-19 04:00:0080176658.86666723.292345


# by 1 Day

func_list = ['first', 'sum', 'mean', 'stddev']
resample_summary(df_1m, 'price', '1D', func_list).head() # by 1 Day
price_1D_firstprice_1D_sumprice_1D_meanprice_1D_stddev
2019-12-19123974655.20277825.946355
2019-12-20264017155.79305625.547419
2019-12-21873973755.19027826.238314
2019-12-22653935054.65277825.675714
2019-12-23693983555.32638926.230239


# by 1 Week
func_list = ['first', 'sum', 'mean', 'stddev']
resample_summary(df_1m, 'price', '1W', func_list) # by 1 Week
price_1W_firstprice_1W_sumprice_1W_meanprice_1W_stddev
2019-12-221215900455.20972225.842990
2019-12-296927294354.15535726.084089
2020-01-057227474054.51190525.840425
2020-01-124127656354.87361126.295806
2020-01-195519709054.73201925.984207


# by 2 Weeks

func_list = ['first', 'sum', 'mean', 'stddev']
resample_summary(df_1m, 'price', '2W', func_list) # by 2 Week
price_2W_firstprice_2W_sumprice_2W_meanprice_2W_stddev
2019-12-221215900455.20972225.842990
2020-01-056954768354.33363125.961867
2020-01-194147365354.81460526.164988


# by 1 Month
func_list = ['first', 'sum', 'mean', 'stddev']
resample_summary(df_1m, 'price', '1M', func_list) # by 1 Month
price_1M_firstprice_1M_sumprice_1M_meanprice_1M_stddev
2019-12-311251003654.49102625.912189
2020-01-314867030454.758925

26.117109





  (6) 10분 단위 구간별 수량 가중 평균 가격 구하기 

   (amount-weighted average of price by 10 minutes time span using pandas resample method)


가격('price')과 수량('amount')을 곱해서 만든 새로운 칼럼 'price_mult_amt' 를 만들어주고, resample('10T') 메소드를 사용해서 10분 단위 구간별로 수량 가중 평균 가격(10분 단위 구간별 구입가격*구입수량 합 / 전체 구입수량 합)을 구해주었습니다.  


참고로, 아래 코드에서 역 슬래쉬('\')는 코드를 한줄에 다 쓰기에 너무 길 경우에 '다음 줄로 넘겨서 쓴 코드를 앞코드와 이어진 코드'로 인식하게 만들어 줄 때 사용합니다.  



# function: weighted average

# 각 시간대의 수량가중평균가격(sum(price*amount)/sum(amount))

# (*가중평균은 특정 시간대에 발생한 모든 구입건의 구입가격*구입수량 합/전체 구입수량 합)


df_summary = pd.DataFrame()


df['price_mult_amt'] = df['price']*df['amount']

df_summary['price_10m_amount_weighted_avg'] = \

    df.price_mult_amt.resample('10T').sum() / df.amount.resample('10T').sum()


df_summary

price_10m_amount_weighted_avg
2019-12-19 00:00:0043.769231
2019-12-19 00:10:0056.222222
2019-12-19 00:20:0064.363636
2019-12-19 00:30:0064.166667




  (7) 10분 단위 구간별 집계/요약 통계량 결과를 csv 파일로 내보내기 

   (exporting summary results by 10 minutes time span into 'csv file' using pandas to_csv() method)


위의 (5)번에서 정의한 resample_summary() 사용자 정의 함수(UDF)를 사용하여 10분 단위('10T') 구간별로 가격('price') 칼럼에 대해 'first', 'last', 'sum', 'cumsum', 'min', 'max', 'mean', 'median', 'range', 'var', 'stddev'를 모두 집계/요약한 데이터 프레임을 만들고, 


이어서, 10단위 구간별로 수량 가중 평균 가격(amount-weighted average of price)을 구한 후에, 


이를 취합한 결과 데이터프레임을 pandas의 to_csv() 메소드를 사용하여 'df_summary.csv' 라는 이름의 csv 파일로 내보내보겠습니다. '년-월-일 시간:분:초'의 시간 정보가 들어있는 index도 같이 내보내야 하므로 to_csv() 메소드 내 index=True 옵션으로 설정해주었으며, 결측값이 존재할 경우 na_rep='NaN' 으로 표기하도록 설정해주었고, 요약통계량 값이 부동소수형(float) 일 경우 소수점 2번째 자리까지만 표기하도록 float_format='%.2f' 옵션을 설정해주었습니다. 



# summary statistics using resample_summary() User Defiened Function, refer to (5)

func_list = ['first', 'last', 'sum', 'cumsum', 'min', 'max', 

            'mean', 'median', 'range', 'var', 'stddev']


df_summary = resample_summary(df, 'price', '10T', func_list)


# amount-weighted average of price, refer to (6)

df['price_mult_amt'] = df['price']*df['amount']

df_summary['price_10m_amount_weighted_avg'] = \

    df.price_mult_amt.resample('10T').sum()/ df.amount.resample('10T').sum()



# export df_summary DataFrame into csv file

import os


work_dir = os.getcwd() # current working directory

file_path = os.path.join(work_dir, 'df_summary.csv')

df_summary.to_csv(file_path

                  , index=True             # include index

                  , na_rep='NaN'           # representation of missing value

                  , float_format = '%.2f') # 2 decimal places

 



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)



728x90
반응형
Posted by Rfriend
,

Lag, Lead window function은 시계열 데이터를 처리할 때 많이 사용하는 매우 유용한 함수입니다. 


이번 포스팅에서는 PostgreSQL, Python (pandas), R (dplyr) 을 이용해서 그룹별로 행을 하나씩 내리기, 올리기 (lag or lead a row by group using PostgreSQL, Python, R) 하는 방법을 소개하겠습니다. 





  1. PostgreSQL로 그룹별로 특정 칼럼의 행을 하나씩 내리기, 올리기 

    (lag, lead a row by group using PostgreSQL lag(), lead() window function)


연월일(dt), 그룹ID(id), 측정값(val) 의 세 개 칼럼을 가진 시계열 데이터의 테이블을 PostgreSQL DB에 만들어보겠습니다. 



DROP TABLE IF EXISTS ts;

CREATE TABLE ts (

    dt date not null

    , id text not null  

    , val numeric not null

);


INSERT INTO ts VALUES 

  ('2019-12-01', 'a', 5)

, ('2019-12-02', 'a', 6)

, ('2019-12-03', 'a', 7)

, ('2019-12-04', 'a', 8)

, ('2019-12-01', 'b', 13)

, ('2019-12-02', 'b', 14)

, ('2019-12-03', 'b', 15)

, ('2019-12-04', 'b', 16);


SELECT * FROM ts ORDER BY id, dt;




PostgreSQL 의 LAG(value, offset, default), LEAD(value, offset, default) Window function을 이용해서 그룹ID('id') 별로 측정값('val')의 행을 하나씩 내리기(lag), 올리기(lead) 해보겠습니다. 행을 내리거나 올린 후에 빈 셀의 값은 'NULL'로 지정해주었습니다. 


LAG(), LEAD() 함수를 사용할 때 그룹ID('id')별로 년월일('dt') 을 기준으로 내림차순 정렬(OVER(PARTITIO BY id ORDER BY dt)) 을 해줍니다. 



-- lead() windows function

SELECT 

    *

    , LAG(val, 1, NULL) OVER (PARTITION BY id ORDER BY dt) AS val_lag_1

    , LEAD(val, 1, NULL) OVER (PARTITION BY id ORDER BY dt) AS val_lead_2

FROM ts;

 



lag(), lead() 함수를 사용해서 lag_1, lead_2 라는 새로운 칼럼을 추가한 'ts_lag_lead' 라는 이름의 테이블을 만들어보겠습니다. 



DROP TABLE IF EXISTS ts_lag_lead;

CREATE TABLE ts_lag_lead AS (

SELECT 

    *

    , LAG(val, 1, NULL) OVER (PARTITION BY id ORDER BY dt) AS val_lag_1

    , LEAD(val, 1, NULL) OVER (PARTITION BY id ORDER BY dt) AS val_lead_2

FROM ts

);


SELECT * FROM ts_lag_lead ORDER BY id, dt;

 





  2. Python pandas 로 DataFrame 내 그룹별 특정 칼럼의 행을 하나씩 내리기, 올리기 

     (shift a row by group using Python pandas library)


위에서 PostgreSQL의 lag(), lead() window function과 똑같은 작업을 Python pandas 를 가지고 수행해보겠습니다. 


먼저 dt, id, val의 칼럼을 가진 pandas DataFrame 시계열 데이터를 만들어보겠습니다. 



import pandas as pd


ts = pd.DataFrame({'dt': ['2019-12-01', '2019-12-02', '2019-12-03', '2019-12-04', 

                          '2019-12-01', '2019-12-02', '2019-12-03', '2019-12-04'], 

                  'id': ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b'], 

                  'val': [5, 6, 7, 8, 13, 14, 15, 16]})


ts

dtidval
02019-12-01a5
12019-12-02a6
22019-12-03a7
32019-12-04a8
42019-12-01b13
52019-12-02b14
62019-12-03b15
72019-12-04b16

 



shift() 함수를 쓰기 전에 sort_values() 함수로 정렬을 해주는데요, lag 는 내림차순 정렬, lead는 오름차순 정렬임에 주의해야 합니다. (PostgreSQL, R 대비 Python이 좀 불편하긴 하네요 -,-;)


(a) lagsort_values() 함수를 이용해서 년월일('dt')를 기준으로 내림차순 정렬 (ascending=True) 한 후, 'id' 그룹별로 'val' 값을 하나씩 내려기 groupby('id')['val'].shift(1)


(b) lead: sort_values() 함수를 이용해서 년월일('dt')를 기준으로 오름차순 정렬 (ascending=False) 한 후, 'id' 그룹별로 'val' 값을 하나씩 올리기 groupby('id')['val].shift(1)



# lag a row by group 'id'

ts['val_lag_1'] =  ts.sort_values(by='dt', ascending=True).groupby('id')['val'].shift(1)


# lead a row by group 'id'

ts['val_lead_1'] = ts.sort_values(by='dt', ascending=False).groupby('id')['val'].shift(1)


ts.sort_values(by=['id', 'dt'])

dtidvalval_lag_1val_lead_1
02019-12-01a5NaN6.0
12019-12-02a65.07.0
22019-12-03a76.08.0
32019-12-04a87.0NaN
42019-12-01b13NaN14.0
52019-12-02b1413.015.0
62019-12-03b1514.016.0
72019-12-04b1615.0NaN

 





  3. R dplyr 로 dataframe 내 그룹별 특정 칼럼의 행을 하나씩 내리기, 올리기 

     (lag, lead a row by group using R dplyr library)



위에서 PostgreSQL의 lag(), lead() window function과 똑같은 작업을 R dplyr library를 가지고 수행해보겠습니다. 


먼저 dt, id, val의 칼럼을 가진 R DataFrame 시계열 데이터를 만들어보겠습니다. 



#install.packages("dplyr")

library(dplyr)


dt <- c(rep(c('2019-12-01', '2019-12-02', '2019-12-03', '2019-12-04'), 2))

id <- c(rep('a', 4), rep('b', 4)) 

val <- c(5, 6, 7, 8, 13, 14, 15, 16)


ts <- data.frame(dt, id, val)

ts

A data.frame: 8 × 3
dtidval
<fct><fct><dbl>
2019-12-01a5
2019-12-02a6
2019-12-03a7
2019-12-04a8
2019-12-01b13
2019-12-02b14
2019-12-03b15
2019-12-04b16

 



R은 Postgresql 처럼 lag(), lead() window function을 가지고 있고 dplyr library의 chain operator를 써서 arrange() 함수로 'dt' 기준 내림차순 정렬하고, group_by(id)를 써서 그룹ID('id')별로 lag(), lead()를 무척 편리하게 적용해서 새로운 변수를 생성(mutate)할 수 있습니다. 



ts <- ts %>% 

    arrange(dt) %>%

    group_by(id)  %>% 

    mutate(val_lag_1 = lag(val, 1), 

          val_lead_1 = lead(val, 1))

 


arrange(ts, id, dt)

A grouped_df: 8 × 5
dtidvalval_lag_1val_lead_1
<fct><fct><dbl><dbl><dbl>
2019-12-01a5NA6
2019-12-02a657
2019-12-03a768
2019-12-04a87NA
2019-12-01b13NA14
2019-12-02b141315
2019-12-03b151416
2019-12-04b1615NA




많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요.



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Jupyter Notebook을 사용하는데 있어 자주 쓰는 것은 아니지만 한번 쓰려고 하면 방법을 찾으려고 하면 또 시간을 빼앗기곤 하는, 그래서 어디에 메모해두었다가 필요할 때 꺼내쓰기에 아기자기한 팁들을 모아보았습니다. 

 

(1) Jupyter Notebook cell 너비 설정하기

(2) Jupyter Notebook에서 DataFrame 칼럼 최대 너비 설정하기

(3) Jupyter Notebook에서 DataFrame 내 텍스트를 왼쪽으로 정렬하기

(4) Jupyter Notebook에서 DataFrame 소수점 자리수 설정하기

(5) Jupyter Notebook에서 matplotlib plot 기본 옵션 설정하기 (figure size, line width, color, grid)

(6) Jupyter Notebook에서 출력하는 행의 최대개수 설정하기 (number of max rows) 

 

 

 (1) Jupyter Notebook cell 너비 설정하기 (setting cell width in Jupyter Notebook)

 

아래 코드의 부분의 숫자를 바꾸어주면 됩니다.  

 

 

from IPython.core.display import display, HTML

 

display(HTML("<style>.container { width: 50% !important; }</style>"))

 

 

 

 

 

 (2) Jupyter Notebook에서 DataFrame 칼럼 최대 너비 설정하기 

     (setting the max-width of DataFrame's column in Jupyter Notebook)

 

 

 

import pandas as pd

pd.set_option('display.max.colwidth', 10)

 

df = pd.DataFrame({'a': [100000000.0123, 20000000.54321], 

                  'b': ['abcdefghijklmnop', 'qrstuvwxyz']})

 

df

 

 
 
Out[2]:
  a b
0 1.0000... abcdef...
1 2.0000... qrstuv...

 

 
 
 
pd.set_option('display.max.colwidth', 50)
 
df
 
Out[3]:
 
 
  a b
0 1.000000e+08 abcdefghijklmnop
1 2.000000e+07 qrstuvwxyz
 

 

 

 

 

 

 (3) Jupyter Notebook에서 DataFrame 내 텍스트를 왼쪽으로 정렬하기 

     (align text of pandas DataFrame to left in Jupyter Notebook)

 

 

dfStyler = df.style.set_properties(**{'text-align': 'left'})

dfStyler.set_table_styles([dict(selector='th', 

                                props=[('text-align', 'left')])])

 

 
 
Out[4]:
  a b
0 1e+08 abcdefghijklmnop
1 2e+07 qrstuvwxyz

 

 

 

 

 

 (4) Jupyter Notebook에서 DataFrame 소수점 자리수 설정하기

     (setting the decimal point format of pandas DataFrame in Jupyter Notebook)

 

아래에 예시로 지수형 표기를 숫자형 표기로 바꾸고, 소수점 2째자리까지만 나타내도록 숫자 표기 포맷을 설정해보겠습니다. 

 

 

import pandas as pd

pd.options.display.float_format = '{:.2f}'.format

 

 

df

 
 
Out[6]:
  a b
0 100000000.01 abcdefghijklmnop
1 20000000.54 qrstuvwxyz

 

 

 

 

 

 (5) Jupyter Notebook에서 matplotlib plot 기본 옵션 설정하기 

    (setting figure size, line width, color, grid of matplotlib plot in Jupyter Notebook)

 

matplotlib.pyplot 의 plt.rcParams[] 를 사용하여 그래프 크기, 선 너비, 선 색깔, 그리드 포함 여부 등을 설정할 수 있습니다. 

 

 

# matplotlib setting

import matplotlib.pylab as plt

%matplotlib inline

 

plt.rcParams["figure.figsize"] = (6, 5)

plt.rcParams["lines.linewidth"] = 2

plt.rcParams["lines.color"] = 'r'

plt.rcParams["axes.grid"] = True

 

# simple plot

x = [1, 2, 3, 4, 5]

y = [2, 3.5, 5, 8, 9]

 

plt.plot(x, y)

plt.show()

 

 

 

 

 

 (6) Jupyter Notebook에서 출력하는 최대 행의 개수 설정하기 (number of max rows)

 

Jupyter Notebook 셀에서 DataFrame 인쇄 시에 기본 설정은 행의 개수가 많을 경우 중간 부분이 점선으로 처리 ("...")되어 건너뛰고, 처음 5개행과 마지막 5개 행만 선별해서 보여줍니다.   

 

 

# if there are many rows, JN does not print all rows

import pandas as pd
import numpy as np

 

df = pd.DataFrame(np.arange(200).reshape(100, 2))
df
 

 

 

 

그런데, 필요에 따라서는 전체 행을 프린트해서 눈으로 확인해봐야할 때도 있습니다. 이럴 경우 Jupyter Notebook에서 출력하는 최대 행의 개수 (the number of max rows in Jupyter Notebook) 를 설정할 수 있습니다. 위의 예에서 5번째 행부터 점선으로 처리되어 건너뛰고 안보여지던 행이, pd.set_option('display.max_rows', 100) 으로 최대 인쇄되는 행의 개수를 100개로 늘려서 설정하니 이번에는 100개 행이 전부 다 인쇄되어 볼 수 있습니다. 

 

 

# setting the number of maximum rows in Jupyter Notebook

import pandas as pd

pd.set_option('display.max_rows', 100)

df
 

 

 

 (7) Jupyter Notebook에서 출력하는 최대 열의 개수 설정하기 (number of max columns)

 

Jupyter Notebook 셀에서 DataFrame 인쇄 시에 기본 설정은 열의 개수가 20개를 넘어가면 중간 부분이 점선으로 처리 ("...")되어 건너뛰고, 나머지 20개만 출력을 해줍니다.  아래의 화면 캡쳐 예시처럼 중간 열 부분의 결과가 "..." 으로 가려져있어서 확인할 수가 없습니다. 

 

 

이럴 경우에 pandas.set_option('display.max.columns', col_num) 옵션을 사용하여 화면에 보여주는 최대 열의 개수 (maximum number of columns)"를 옵션으로 설정해줄 수 있습니다.   위에서는 ... 으로 처리되었던 열10 ~ 열12 가 아래에서는 제대로 jupyter notebook에 출력되었네요!

 

 

# setting the maximum number of columns in jupyter notebook display

import pandas as pd

 

pd.set_option('display.max.columns', 50) # set the maximum number whatever you want
 

 

 

많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~

'를 꾹 눌러주세요. :-)

 

 

728x90
반응형
Posted by Rfriend
,

Python은 Jupyter Notebook, R은 RStudio 라고만 알고 있는 분도 계실텐데요, IRKernel을 설치해주면 Jupyter Notebook에서도 R을 사용할 수 있습니다


이번 포스팅에서는 Jupyter Notebook에서 R을 사용할 수 있도록 하는 방법을 소개하겠습니다. 

(MacOS, Anaconda, Python3.6 version 환경)


명령 프롬프트 창(prompt window, terminal) 에서 아래의 절차에 따라서 IRKernel을 설치해주시면 됩니다. 

(RStudion에서 설치를 하려고 하면 에러가 납니다. 명령 프롬프트창/ Terminal에서 진행하기 바랍니다)



  1. Jupyter Notebook 사용할 수 있도록 IRKernel 설치하기



(1) 명령 프롬프트 창(terminal)에서 'R' 을 써주고 엔터 => R을 실행합니다. 



MacBook-Pro:~ ihongdon$

MacBook-Pro:~ ihongdon$ R


R version 3.6.0 (2019-04-26) -- "Planting of a Tree"

Copyright (C) 2019 The R Foundation for Statistical Computing

Platform: x86_64-apple-darwin15.6.0 (64-bit)

 





(2) devtools 패키지를 설치합니다: install.packages('devtools')


'현재 세션에서 사용할 CRAN 미러를 선택해 주세요' 라는 메시지와 함께 콤보 박스 창이 뜨면 서버 아무거나 선택하면 됩니다. 저는 'SEOUL' 선택했습니다. 



>

> install.packages('devtools')

--- 현재 세션에서 사용할 CRAN 미러를 선택해 주세요 ---


SEOUL



경고: 저장소 https://cran.seoul.go.kr/bin/macosx/el-capitan/contrib/3.6에 대한 인덱스에 접근할 수 없습니다:

  URL 'https://cran.seoul.go.kr/bin/macosx/el-capitan/contrib/3.6/PACKAGES'를 열 수 없습니다

소스형태의 패키지 ‘devtools’(들)를 설치합니다.


URL 'https://cran.seoul.go.kr/src/contrib/devtools_2.2.1.tar.gz'을 시도합니다

Content type 'application/x-gzip' length 372273 bytes (363 KB)

==================================================

downloaded 363 KB


* installing *source* package ‘devtools’ ...

** 패키지 ‘devtools’는 성공적으로 압축해제되었고, MD5 sums 이 확인되었습니다

** using staged installation

** R

** inst

** byte-compile and prepare package for lazy loading

** help

*** installing help indices

*** copying figures

** building package indices

** installing vignettes

** testing if installed package can be loaded from temporary location

** testing if installed package can be loaded from final location

** testing if installed package keeps a record of temporary installation path

* DONE (devtools)


다운로드한 소스 패키지들은 다음의 위치에 있습니다

‘/private/var/folders/6q/mtq6ftrj6_z4txn_zsxcfyxc0000gn/T/Rtmpg1bePN/downloaded_packages’

>

 




(3) IRkernel 설치: devtools::install_github('IRkernel/IRkernel') 실행합니다. 



>

> devtools::install_github('IRkernel/IRkernel')

Downloading GitHub repo IRkernel/IRkernel@master

'/usr/local/bin/git' clone --depth 1 --no-hardlinks --recurse-submodules https://github.com/jupyter/jupyter_kernel_test.git /var/folders/6q/mtq6ftrj6_z4txn_zsxcfyxc0000gn/T//Rtmpg1bePN/remotes36921150b621/IRkernel-IRkernel-67592db/tests/testthat/jkt

'/var/folders/6q/mtq6ftrj6_z4txn_zsxcfyxc0000gn/T//Rtmpg1bePN/remotes36921150b621/IRkernel-IRkernel-67592db/tests/testthat/jkt'에 복제합니다...

remote: Enumerating objects: 12, done.

remote: Counting objects: 100% (12/12), done.

remote: Compressing objects: 100% (11/11), done.

remote: Total 12 (delta 1), reused 3 (delta 0), pack-reused 0

오브젝트 묶음 푸는 중: 100% (12/12), 완료.

'/usr/local/bin/git' clone --depth 1 --no-hardlinks --recurse-submodules https://github.com/flying-sheep/ndjson-testrunner.git /var/folders/6q/mtq6ftrj6_z4txn_zsxcfyxc0000gn/T//Rtmpg1bePN/remotes36921150b621/IRkernel-IRkernel-67592db/tests/testthat/njr

'/var/folders/6q/mtq6ftrj6_z4txn_zsxcfyxc0000gn/T//Rtmpg1bePN/remotes36921150b621/IRkernel-IRkernel-67592db/tests/testthat/njr'에 복제합니다...

remote: Enumerating objects: 10, done.

remote: Counting objects: 100% (10/10), done.

remote: Compressing objects: 100% (8/8), done.

remote: Total 10 (delta 0), reused 6 (delta 0), pack-reused 0

오브젝트 묶음 푸는 중: 100% (10/10), 완료.

Skipping 1 packages ahead of CRAN: htmltools

   checking for file ‘/private/var/folders/6q/mtq6ftrj6_z4txn_zsxcfyxc0000gn/T/Rtmpg1bePN/remotes36921150b621/IRkernel✔  checking for file ‘/private/var/folders/6q/mtq6ftrj6_z4txn_zsxcfyxc0000gn/T/Rtmpg1bePN/remotes36921150b621/IRkernel-IRkernel-67592db/DESCRIPTION’

─  preparing ‘IRkernel’:

✔  checking DESCRIPTION meta-information ...

─  checking for LF line-endings in source and make files and shell scripts

─  checking for empty or unneeded directories

   Removed empty directory ‘IRkernel/example-notebooks’

─  building ‘IRkernel_1.0.2.9000.tar.gz’


* installing *source* package ‘IRkernel’ ...

** using staged installation

** R

** inst

** byte-compile and prepare package for lazy loading

** help

*** installing help indices

** building package indices

** testing if installed package can be loaded from temporary location

** testing if installed package can be loaded from final location

** testing if installed package keeps a record of temporary installation path

* DONE (IRkernel)

>

 




(4) IRkernel::installspec()  확인



>

> IRkernel::installspec()

[InstallKernelSpec] Installed kernelspec ir in /Users/ihongdon/Library/Jupyter/kernels/ir

>

 



만약 아래와 같은 메시지가 떴다면 이는 아마도 위의 (1), (2), (3) 절차를 "명령 프롬프트 창(Terminal)"에서 실행한 것이 아니라 "RStudio"에서 실행했기 때문일 것입니다. 


jupyter-client has to be installed but “jupyter kernelspec –version” exited with code 127.


위의 메시지가 떴다면 명령 프롬프트 창(prompt window, Terminal)을 하나 열고 위의 (1), (2), (3) 절차를 실행한 후에 (4)로 확인을 해보세요. 



Windows10 OS를 사용하는 분이라면 PATH에 아래 경로를 추가로 등록해보시기 바랍니다. 


 

 Anaconda\Lib\site-packages\jupyter_client


 C:\Users\Anaconda3\Scripts 



참고로 Windows 10, Windows 8에서 PATH 등록하는 법은 아래와 같습니다. 

[ Windows10 또는 Windows8 에서 PATH 등록 ]


  1. 시스템(제어판)’ 선택
  2. 고급 시스템 설정 링크선택(클릭)
  3. 환경변수선택 —> 시스템 변수 섹션에서 ‘PATH 환경변수선택 —> ‘편집선택 —> PATH 환경변수가 존재하지 않을 경우새로 만들기선택
  4. 시스템 변수 편집 (또는 시스템 변수)’ 창에서 PATH 환경 변수의 값을 지정 —> ‘확인선택 —> 나머지 모두 닫기





  2. Jupyter Notebook에서 R 사용해보기


R kernel 도 잘 설치가 되었으니 Jupyter Notebook에서 R을 사용해보겠습니다.  위의 R이 실행 중인 terminal 에서 'ctrl + z' 를 눌러서 빠져나옵니다. 


$ conda info -e (혹은 conda env list) 로 가상환경 목록을 확인하고, 

$ source activate [가상환경 이름] 으로 특정 가상환경으로 들어갑니다. 

(Windows OS 에서는 activate [가상환경 이름])

$ jupyter notebook 로 jupyter notebook 창을 열어줍니다. 



>

>

[1]+  Stopped                 R

MacBook-Pro:~ ihongdon$

MacBook-Pro:~ ihongdon$ conda info -e

# conda environments:

#

base                  *  /Users/ihongdon/anaconda3

py2.7_tf1.4              /Users/ihongdon/anaconda3/envs/py2.7_tf1.4

py3.5_tf1.4              /Users/ihongdon/anaconda3/envs/py3.5_tf1.4

py3.6_tf2.0              /Users/ihongdon/anaconda3/envs/py3.6_tf2.0


MacBook-Pro:~ ihongdon$

MacBook-Pro:~ ihongdon$

MacBook-Pro:~ ihongdon$ source activate py3.6_tf2.0

(py3.6_tf2.0) MacBook-Pro:~ ihongdon$

(py3.6_tf2.0) MacBook-Pro:~ ihongdon$ jupyter notebook

 




아래처럼 Jupyter Notebook 창이 뜨면 오른쪽 상단의 'New' 메뉴를 클릭하고, 여러 하위 메뉴 중 'R' 선택합니다. 





간단한 예제로 x, y 두개 변수로 구성된 R 데이터프레임을 하나 만들고, ggplot2 로 산점도를 그려보았습니다. 






다음으로 종속변수 y에 대해 설명변수 x를 사용한 선형회귀모형을 적합시켜 보았습니다. 




많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)





728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python pandas DataFrame을 만들려고 할 때 "ValueError: If using all scalar values, you must pass an index" 에러 해결 방안 4가지를 소개하겠습니다.

아래의 예처럼 dictionary로 키, 값 쌍으로 된 데이터를 pandas DataFrame으로 만들려고 했을 때, 모든 값이 스칼라 값(if using all scalar values) 일 경우에 "ValueError: If using all scalar values, you must pass an index" 에러가 발생합니다. 

import pandas as pd

df = pd.DataFrame({'col_1': 1, 

                  'col_2': 2})

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-73d6f192ba2a> in <module>()
      1 df = pd.DataFrame({'col_1': 1, 
----> 2                   'col_2': 2})

~/anaconda3/envs/py3.5_tf1.4/lib/python3.5/site-packages/pandas/core/frame.py in __init__(self, data, index, columns, dtype, copy)
    273                                  dtype=dtype, copy=copy)
    274         elif isinstance(data, dict):
--> 275             mgr = self._init_dict(data, index, columns, dtype=dtype)
    276         elif isinstance(data, ma.MaskedArray):
    277             import numpy.ma.mrecords as mrecords

~/anaconda3/envs/py3.5_tf1.4/lib/python3.5/site-packages/pandas/core/frame.py in _init_dict(self, data, index, columns, dtype)
    409             arrays = [data[k] for k in keys]
    410 
--> 411         return _arrays_to_mgr(arrays, data_names, index, columns, dtype=dtype)
    412 
    413     def _init_ndarray(self, values, index, columns, dtype=None, copy=False):

~/anaconda3/envs/py3.5_tf1.4/lib/python3.5/site-packages/pandas/core/frame.py in _arrays_to_mgr(arrays, arr_names, index, columns, dtype)
   5494     # figure out the index, if necessary
   5495     if index is None:
-> 5496         index = extract_index(arrays)
   5497     else:
   5498         index = _ensure_index(index)

~/anaconda3/envs/py3.5_tf1.4/lib/python3.5/site-packages/pandas/core/frame.py in extract_index(data)
   5533 
   5534         if not indexes and not raw_lengths:
-> 5535             raise ValueError('If using all scalar values, you must pass'
   5536                              ' an index')
   5537 

ValueError: If using all scalar values, you must pass an index




이 에러를 해결하기 위한 4가지 방법을 차례대로 소개하겠습니다. 


 (1) 해결방안 1 : 인덱스 값을 설정해줌 (pass an index)

에러 메시지에 "you must pass an index" 라는 가이드라인대로 인덱스 값을 추가로 입력해주면 됩니다. 


# (1) pass an index

df = pd.DataFrame({'col_1': 1, 

                  'col_2': 2}

                  index = [0])


df

col_1col_2
012

 


물론 index 에 원하는 값을 입력해서 설정해줄 수 있습니다. index 에 'row_1' 이라고 해볼까요?

df = pd.DataFrame({'col_1': 1, 

                   'col_2': 2}

                  index = ['row_1'])


df

col_1col_2
row_112



 (2) 스칼라 값 대신 리스트 값을 입력 (use a list instead of scalar values)

입력하는 값(values)에 대괄호 [ ] 를 해주어서 리스트로 만들어준 값을 사전형의 값으로 사용하면 에러가 발생하지 않습니다. 

# (2) use a list instead of scalar values

df2 = pd.DataFrame({'col_1': [1]

                    'col_2': [2]})


df2

col_1col_2
012



 (3) pd.DataFrame.from_records([{'key': value}]) 를 사용해서 DataFrame 만들기

이때도 [ ] 로 해서 리스트 값을 입력해주어야 합니다. ( [ ] 빼먹으면 동일 에러 발생함)

# (3) use pd.DataFrame.from_records() with a list

df3 = pd.DataFrame.from_records([{'col_1': 1, 

                                  'col_2': 2}])


df3 

col_1col_2
012



 (4) pd.DataFrame.from_dict([{'key': value}]) 를 사용하여 DataFrame 만들기

(3)과 거의 유사한데요, from_records([]) 대신에 from_dict([]) 를 사용하였으며, 역시 [ ] 로 해서 리스트 값을 입력해주면 됩니다. 

# (4) use pd.DataFrame.from_dict([]) with a list

df4 = pd.DataFrame.from_dict([{'col_1': 1, 

                              'col_2': 2}])


df4

col_1col_2
012


많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 웹에서 JSON 포맷 파일을 읽어와서 pandas DataFrame으로 변환하는 방법에 대해서 소개하였습니다. 


이번 포스팅에서는 JSON과 함께 웹 애플리케이션에서 많이 사용하는 데이터 포맷인 XML (Extensible Markup Language) 을 Python을 사용하여 웹으로 부터 읽어와서 파싱(parsing XML), pandas DataFrame으로 변환하여 분석과 시각화하는 방법을 소개하겠습니다. 


XML (Extensible Markup Language) 인간과 기계가 모두 읽을 수 있는 형태로 문서를 인코딩하는 규칙의 집합을 정의하는 마크업 언어(Markup Language) 입니다. XML의 설계 목적은 단순성, 범용성, 인터넷에서의 활용성을 강조점을 둡니다. XML은 다양한 인간 언어들을 유니코드를 통해 강력하게 지원하는 텍스트 데이터 포맷입니다. 비록 XML의 설계가 문서에 중점을 두고는 있지만, XML은 임의의 데이터 구조를 띠는 웹 서비스와 같은 용도의 재표현을 위한 용도로 광범위하게 사용되고 있습니다. 

- from wikipedia (https://en.wikipedia.org/wiki/XML) -


XML 은 아래와 같이 생겼는데요, HTML, JSON과 왠지 비슷하게 생겼지요? 



[ XML format data 예시 ]



<CATALOG>
<CD>
<TITLE>Empire Burlesque</TITLE>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR>
</CD>
<CD>
<TITLE>Hide your heart</TITLE>
<ARTIST>Bonnie Tyler</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>CBS Records</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1988</YEAR>
</CD>

<CD>

<TITLE>Unchain my heart</TITLE>
<ARTIST>Joe Cocker</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>EMI</COMPANY>
<PRICE>8.20</PRICE>
<YEAR>1987</YEAR>
</CD>

</CATALOG> 


- source: https://www.w3schools.com/xml/cd_catalog.xml -





[ Python으로 웹에서 XML 데이터를 읽어와서 pandas DataFrame으로 만들기 코드 예제 ]




(1) Import Libraries


먼저 XML 을 파싱하는데 필요한 xml.etree.ElementTree 모듈과 웹 사이트에 접속해서 XML 파일을 읽을 수 있도록 요청하는 urllib 모듈을 불러오겠습니다. XML 데이터를 나무(Tree)에 비유해서 뿌리(root)부터 시작하여 줄기, 가지, 잎파리까지 단계적으로 파싱한다는 의미에서 모듈 이름이 xml.etree.ElementTree 라고 생각하면 됩니다. 



import pandas as pd

import xml.etree.ElementTree as ET


import sys

if sys.version_info[0] == 3:

    from urllib.request import urlopen

else:

    from urllib import urlopen




Python 3.x 버전에서는 'from urllib.request import urlopen'으로 urllib 모듈의 request 메소드를 import해야 하며, 만약 Python 3.x 버전에서 아래처럼 'from urllib import urlopen' 을 사용하면 'ImportError: cannot import name 'urlopen'' 이라는 ImportError가 발생합니다. 



# If you are using Python 3.x version, then ImportError will be raised as below

from urllib import urlopen

---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-2-dbf1dbb53f94> in <module>()
----> 1 from urllib import urlopen

ImportError: cannot import name 'urlopen'





(2) Open URL and Read XML data from Website URL


이제 "https://www.w3schools.com/xml/cd_catalog.xml" 사이트에서 XML 포맷의 CD catalog 정보를 문자열(string)로 읽어와보겠습니다. 



url = "https://www.w3schools.com/xml/cd_catalog.xml"

response = urlopen(url).read()

xtree = ET.fromstring(response)


xtree

<Element 'CATALOG' at 0x00000219E77DCCC8>

 




(3) Parsing XML data into text by iterating through each node of the tree


다음으로 for loop을 돌면서 나무의 노드들(nodes of tree)에서 필요한 정보를 찾아 파싱(find and parse XML data)하여 텍스트 데이터(text)로 변환하여 사전형(Dictionary)의 키, 값의 쌍으로 차곡차곡 저장(append)을 해보겠습니다



rows = []


# iterate through each node of the tree

for node in xtree:

    n_title = node.find("TITLE").text

    n_artist = node.find("ARTIST").text

    n_country = node.find("COUNTRY").text

    n_company = node.find("COMPANY").text

    n_price = node.find("PRICE").text

    n_year = node.find("YEAR").text

    

    rows.append({"title": n_title, 

                 "artist": n_artist, 

                 "country": n_country, 

                 "company": n_company, 

                 "price": n_price, 

                 "year": n_year})

 




(4) Convert XML text data into pandas DataFrame



# convert XML data to pandas DataFrame

columns = ["title", "artist", "country", "company", "price", "year"]

catalog_cd_df = pd.DataFrame(rows, columns = columns)


catalog_cd_df.head(10)

titleartistcountrycompanypriceyear
0Empire BurlesqueBob DylanUSAColumbia10.901985
1Hide your heartBonnie TylerUKCBS Records9.901988
2Greatest HitsDolly PartonUSARCA9.901982
3Still got the bluesGary MooreUKVirgin records10.201990
4ErosEros RamazzottiEUBMG9.901997
5One night onlyBee GeesUKPolydor10.901998
6Sylvias MotherDr.HookUKCBS8.101973
7Maggie MayRod StewartUKPickwick8.501990
8RomanzaAndrea BocelliEUPolydor10.801996
9When a man loves a womanPercy SledgeUSAAtlantic8.701987





(5) Change data type from string object to float64, int32 for numeric data


아래에 df.dtypes 로 각 칼럼의 데이터 형태를 확인해보니 전부 문자열 객체(string object)입니다. astype()을 이용하여 칼럼 중에서 price는 float64, year는 int32로 변환을 해보겠습니다. 



catalog_cd_df.dtypes

title      object
artist     object
country    object
company    object
price      object
year       object
dtype: object


import numpy as np

catalog_cd_df = catalog_cd_df.astype({'price': np.float

                                      'year': int})


catalog_cd_df.dtypes

title       object
artist      object
country     object
company     object
price      float64
year         int32
dtype: object





(6) Calculate mean value of price by Country and plot bar plot it



country_mean = catalog_cd_df.groupby('country').price.mean()

country_mean

country
EU        9.320000
Norway    7.900000
UK        8.984615
USA       9.385714
Name: price, dtype: float64



country_mean_df = pd.DataFrame(country_mean).reset_index()


import seaborn as sns

sns.barplot(x='country', y='price', data=country_mean_df)

plt.show()


 



이상으로 웹에서 XML 데이터를 Python으로 읽어와서 파싱 후 pandas DataFrame으로 변환하는 방법에 대한 소개를 마치겠습니다. 



Python으로 JSON 파일 읽기, 쓰기는 https://rfriend.tistory.com/474 를 참고하세요. 

Python으로 YAML 파일 읽기, 쓰기는 https://rfriend.tistory.com/540 를 참고하세요. 


많은 도움이 되었기를 바랍니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 Python의 json.dumps() 를 사용해서 JSON 포맷 데이터를 쓰거나, json.loads()를 사용해서 JSON 포맷 데이터를 python으로 읽어오는 방법(https://rfriend.tistory.com/474)을 소개하였습니다. 


이번 포스팅에서는 이어서 웹에 있는 JSON 포맷 데이터를 Python으로 읽어와서 pandas DataFrame으로 만드는 방법(How to read JSON formate data from WEB API and convert it to pandas DataFrame in python)을 소개하겠습니다. 





JSON 포맷 파일을 가져올 수 있는 사이트로 "Awesome JSON Datasets (https://github.com/jdorfman/awesome-json-datasets)" 를 예로 들어서 설명해보겠습니다. 


여러개의 JSON Datasets 이 올라가 있는데요, 이중에서 'Novel Prize' JSON 포맷 데이터(http://api.nobelprize.org/v1/prize.json)를 읽어와서 DataFrame으로 만들어보겠습니다. 




 (1) API 웹 사이트에서 JSON 포맷 자료를 Python으로 읽어오기



이제 urllib 모듈의 rulopen 함수를 사용해서 JSON 데이터가 있는 URL로 요청(request)을 보내서 URL을 열고 JSON 데이터를 읽어와서, python의 json.loads() 를 사용하여 novel_prize_json 이라 이름의 Python 객체로 만들어보겠습니다.  



# parse a JSON string using json.loads() method : returns a dictionary

import json

import urllib

import pandas as pd


# API request to the URL

import sys


if sys.version_info[0] == 3:

    from urllib.request import urlopen # for Python 3.x

else:

    from urllib import urlopen           # for Python 2.x


with urlopen("http://api.nobelprize.org/v1/prize.json") as url:

    novel_prize_json_file = url.read()




urllib 모듈의 (web open) request 메소드를 불러오 때 Python 2.x 버전에서는 from urllib import urlopen 을 사용하는 반면, Python 3.x 버전에서는 from urllib.request import urlopen 을 사용합니다. 따라서 만약 Python 3.x 사용자가 아래처럼 (Python 2.x 버전에서 사용하는) from urllib import urlopen 이라고 urlopen을 importing 하려고 하면 ImportError: cannot import name 'urlopen' 이라는 에러가 납니다. 


# ImportError: cannot import name 'urlopen' at python 3.x

from urllib import urlopen # It's only for Python 2.x. It's not working at Python 3.x


with urlopen("http://api.nobelprize.org/v1/prize.json") as url:

    novel_prize_json_file = url.read()


---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-1-81c8fae1a1fd> in <module>()
      1 # API request to the URL
----> 2 from urllib import urlopen
      3 
      4 with urlopen("http://api.nobelprize.org/v1/prize.json") as url:
      5     novel_prize_json_file = url.read()

ImportError: cannot import name 'urlopen'



다음으로, 위에서 읽어온 JSON 포맷 데이터를 Python의 json.loads() 메소드를 이용해서 decoding 해보겠습니다. 이때 decode('utf-8') 로 설정해주었습니다. 


# decoding to python object

novel_prize_json = json.loads(novel_prize_json_file.decode('utf-8'))



decoding을 할 때 'utf-8' 을 설정을 안해주니 아래처럼 TypeError 가 나네요. (TypeError: the JSON object must be str, not 'builtin_function_or_method')


# decoding TypeError. decode using decode('utf-8')

novel_prize_json = json.loads(novel_prize_json_file.decode)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-1fda68be6386> in <module>()
      1 # decoding TypeError
----> 2 novel_prize_json = json.loads(novel_prize_json_file.decode)

C:\Users\admin\Anaconda3\lib\json\__init__.py in loads(s, encoding, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    310     if not isinstance(s, str):
    311         raise TypeError('the JSON object must be str, not {!r}'.format(
--> 312                             s.__class__.__name__))
    313     if s.startswith(u'\ufeff'):
    314         raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)",

TypeError: the JSON object must be str, not 'builtin_function_or_method'




Novel Prize JSON 파일을 Python 객체로 읽어왔으니, keys() 메소드로 키를 확인해보겠습니다. 그리고 'prizes' 키의 첫번째 데이터(novel_prize_json['prizes'][0])인 물리학(Physics) 분야 노벨상 수상자 정보를 인쇄해보겠습니다. 



novel_prize_json.keys()

dict_keys(['prizes'])


novel_prize_json['prizes'][0].keys()

dict_keys(['laureates', 'year', 'overallMotivation', 'category'])


novel_prize_json['prizes'][0]

{'category': 'physics',
 'laureates': [{'firstname': 'Arthur',
   'id': '960',
   'motivation': '"for the optical tweezers and their application to biological systems"',
   'share': '2',
   'surname': 'Ashkin'},
  {'firstname': 'Gérard',
   'id': '961',
   'motivation': '"for their method of generating high-intensity, ultra-short optical pulses"',
   'share': '4',
   'surname': 'Mourou'},
  {'firstname': 'Donna',
   'id': '962',
   'motivation': '"for their method of generating high-intensity, ultra-short optical pulses"',
   'share': '4',
   'surname': 'Strickland'}],
 'overallMotivation': '"for groundbreaking inventions in the field of laser physics"', 

'year': '2018'} 




가독성을 높이기 위해서 json.dumps(obj, indent=4) 를 사용해서 4칸 들여쓰기 (indentation)을 해보겠습니다. 



print(json.dumps(novel_prize_json['prizes'][0], indent=4))

{
    "laureates": [
        {
            "share": "2",
            "id": "960",
            "surname": "Ashkin",
            "motivation": "\"for the optical tweezers and their application to biological systems\"",
            "firstname": "Arthur"
        },
        {
            "share": "4",
            "id": "961",
            "surname": "Mourou",
            "motivation": "\"for their method of generating high-intensity, ultra-short optical pulses\"",
            "firstname": "G\u00e9rard"
        },
        {
            "share": "4",
            "id": "962",
            "surname": "Strickland",
            "motivation": "\"for their method of generating high-intensity, ultra-short optical pulses\"",
            "firstname": "Donna"
        }
    ],
    "year": "2018",
    "overallMotivation": "\"for groundbreaking inventions in the field of laser physics\"",
    "category": "physics"
}

 



키(keys)를 기준으로 정렬하는 것까지 포함해서 다시 한번 프린트를 해보겠습니다. 



print(json.dumps(novel_prize_json['prizes'][0], indent=4, sort_keys=True))

{
    "category": "physics",
    "laureates": [
        {
            "firstname": "Arthur",
            "id": "960",
            "motivation": "\"for the optical tweezers and their application to biological systems\"",
            "share": "2",
            "surname": "Ashkin"
        },
        {
            "firstname": "G\u00e9rard",
            "id": "961",
            "motivation": "\"for their method of generating high-intensity, ultra-short optical pulses\"",
            "share": "4",
            "surname": "Mourou"
        },
        {
            "firstname": "Donna",
            "id": "962",
            "motivation": "\"for their method of generating high-intensity, ultra-short optical pulses\"",
            "share": "4",
            "surname": "Strickland"
        }
    ],
    "overallMotivation": "\"for groundbreaking inventions in the field of laser physics\"",
    "year": "2018"
}

 




 (2) JSON 포맷 데이터를 pandas DataFrame으로 만들기


다음으로 Python으로 불러온 JSON 포맷의 데이터 중의 일부분을 indexing하여 pandas DataFrame으로 만들어보겠습니다. 


예로, ['prizes'][0] 은 물리학(physics) 노벨상이며, ['prizes'][0]['laureates'] 로 물리학 노벨상 수상자 정보만 선별해서 pd.DataFrame() 으로 DataFrame을 만들어보겠습니다. 


novel_prize_physics = pd.DataFrame(novel_prize_json['prizes'][0]["laureates"])


novel_prize_physics

firstnameidmotivationsharesurname
0Arthur960"for the optical tweezers and their applicatio...2Ashkin
1Gérard961"for their method of generating high-intensity...4Mourou
2Donna962"for their method of generating high-intensity...4Strickland



DataFrame을 만들 때 칼럼 순서를 columns 로 지정을 해줄 수 있습니다. 


novel_prize_physics = pd.DataFrame(novel_prize_json['prizes'][0]["laureates"]

                                   columns = ['id', 'firstname', 'surname', 'share', 'motivation'])


novel_prize_physics

idfirstnamesurnamesharemotivation
0960ArthurAshkin2"for the optical tweezers and their applicatio...
1961GérardMourou4"for their method of generating high-intensity...
2962DonnaStrickland4"for their method of generating high-intensity...


많은 도움이 되었기를 바랍니다 .


다음 포스팅에서는 웹에서 XML 포맷 데이터를 Python으로 읽어와서 pandas DataFrame으로 만드는 방법을 소개하겠습니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



728x90
반응형
Posted by Rfriend
,

json.org의 JSON 소개 내용에 따르면, JSON (JavaScript Object Notation) 은 XML, YAML 과 함께 효율적으로 데이터를 저장하고 교환(exchange data)하는데 사용하는 텍스트 데이터 포맷 중의 하나입니다. JSON은 사람이 읽고 쓰기에 쉬우며, 또한 기계가 파싱하고 생성하기도에 쉽습니다. JSON은 그 이름에서 유추할 수 있듯이 JavaScript의 프로그래밍 언어의 부분에 기반하고 있으며, C-family 프로그램밍 언어 (C, C++, C#, Java, JavaScript, Perl, Python 등)의 규약을 따르고 있어서 C-family 프로그래밍 언어 간 데이터를 교환하는데 적합합니다. 

JSON은 아래의 두개의 구조로 이루어져 있습니다. 

  • 이름/값 쌍의 집합 (A collection of name/value pairs): object, record, struct, dictionary, hash table, keyed list, associative array
  • 정렬된 값의 리스트 (An ordered list of values): array, vector, list, sequence

홍길동이라는 이름의 학생에 대한 정보를 포함하고 있는 JSON 데이터 포맷의 예를 들어보겠습니다.

{

    "1.FirstName": "Gildong",
    "2.LastName": "Hong",
    "3.Age": 20,
    "4.University": "Yonsei University",
    "5.Courses": [
        {
            "Classes": [
                "Probability",
                "Generalized Linear Model",
                "Categorical Data Analysis"
            ],
            "Major": "Statistics"
        },
        {
            "Classes": [
                "Data Structure",
                "Programming",
                "Algorithms"
            ],
            "Minor": "ComputerScience"
        }
    ]
}


그러면, 이번 포스팅에서는 

(1) Python 객체를 JSON 데이터로 쓰기, 직렬화, 인코딩 (Write Python object to JSON, Serialization, Encoding)

(2) JSON 포맷 데이터를 Python 객체로 읽기, 역직렬화, 디코딩 (Read JSON to Python, Deserialization, Decoding)

하는 방법을 소개하겠습니다. 


위의 Python - JSON 간 변환 표(conversion table b/w python and JSON)에서 보는 바와 같이, python의 list, tuple 이 JSON의 array로 변환되며, JSON의 array는 pythonhon의 list로 변환됩니다. 따라서 Python의 tuple을 JSON으로 변환하면 JSON array가 되며, 이를 다시 Python으로 재변환하면 이땐 python의 tuple이 아니라 list로 변환된다는 점은 인식하고 사용하기 바랍니다. 


 (1) Python 객체를 JSON 데이터로 쓰기, 직렬화, 인코딩: json.dumps()
      (Write Python object to JSON, Serialization, Encoding)

python 객체를 JSON 데이터로 만들어서 쓰기 위해서는 파이썬의 내장 json 모듈이 필요합니다. 

 import json


아래와 같은 홍길동 이라는 학생의 정보를 담고 있는 사전형 자료(dictionary)를 json.dump()와 json.dumps() 의 두가지 방법으로 JSON 포맷 데이터로 만들어보겠습니다. 

student_data = {
    "1.FirstName": "Gildong",
    "2.LastName": "Hong",
    "3.Age": 20, 
    "4.University": "Yonsei University",
    "5.Courses": [
        {
            "Major": "Statistics", 
            "Classes": ["Probability", 
                        "Generalized Linear Model", 
                        "Categorical Data Analysis"]
        }, 
        {
            "Minor": "ComputerScience", 
            "Classes": ["Data Structure", 
                        "Programming", 
                        "Algorithms"]
        }
    ]
} 


(2-1) with open(): json.dump() 를 사용해서 JSON 포맷 데이터를 디스크에 쓰기

with open("student_file.json", "w") 로 "student_file.json" 이름의 파일을 쓰기("w") 모드로 열어놓고, json.dump(student_data, json_file) 로 직렬화해서 JSON으로 내보내고자 하는 객체 student_data를, 직렬화된 데이터가 쓰여질 파일 json_file 에 쓰기를 해주었습니다. 

import json

with open("student_file.json", "w") as json_file:

    json.dump(student_data, json_file)


그러면 아래의 화면캡쳐에서 보는 바와 같이 'student_file.json'이라는 이름의 JSON 포맷 데이터가 새로 생성되었음을 알 수 있습니다.



(2-2) json.dumps()를 사용해서 JSON 포맷 데이터를 메모리에 만들기

만약 메모리 상에 JSON 포맷 데이터를 만들어놓고 python에서 계속 작업을 하려면 json.dumps() 를 사용합니다.

import json

st_json = json.dumps(student_data)

print(st_json)

{"5.Courses": [{"Classes": ["Probability", "Generalized Linear Model", "Categorical Data Analysis"], "Major": "Statistics"}, {"Minor": "ComputerScience", "Classes": ["Data Structure", "Programming", "Algorithms"]}], "3.Age": 20, "2.LastName": "Hong", "4.University": "Yonsei University", "1.FirstName": "Gildong"}


이때 만약 json.dumps()가 아니라 json.dump() 처럼 's'를 빼먹으면 TypeError가 발생하므로 주의하세요. 

# use json.dumps() instead of json.dump()

st_json = json.dump(student_data)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-ac881d00bbcb> in <module>()
----> 1 st_json = json.dump(student_data)

TypeError: dump() missing 1 required positional argument: 'fp'


json.dumps()로 파이썬 객체를 직렬화해서 JSON으로 쓸 때 사람이 보기에 좀더 쉽도록 'indent = int' 로 들여쓰기(indentation) 옵션을 설정할 수 있습니다. 아래 예시는 indent=4 로 설정한건데요, 한결 보기에 가독성이 좋아졌습니다. 


import json

st_json2 = json.dumps(student_data, indent=4)

print(st_json2)

{

    "3.Age": 20,
    "5.Courses": [
        {
            "Classes": [
                "Probability",
                "Generalized Linear Model",
                "Categorical Data Analysis"
            ],
            "Major": "Statistics"
        },
        {
            "Minor": "ComputerScience",
            "Classes": [
                "Data Structure",
                "Programming",
                "Algorithms"
            ]
        }
    ],
    "1.FirstName": "Gildong",
    "4.University": "Yonsei University",
    "2.LastName": "Hong"
}


'sort_keys=True' 를 설정해주면 키(keys)를 기준으로 정렬해서 직렬화하여 내보낼 수도 있습니다. 

import json

st_json3 = json.dumps(student_data, indent=4, sort_keys=True)

print(st_json3)

{
    "1.FirstName": "Gildong",
    "2.LastName": "Hong",
    "3.Age": 20,
    "4.University": "Yonsei University",
    "5.Courses": [
        {
            "Classes": [
                "Probability",
                "Generalized Linear Model",
                "Categorical Data Analysis"
            ],
            "Major": "Statistics"
        },
        {
            "Classes": [
                "Data Structure",
                "Programming",
                "Algorithms"
            ],
            "Minor": "ComputerScience"
        }
    ]
}




 (2) JSON 포맷 데이터를 Python 객체로 읽기, 역직렬화, 디코딩: json.loads()
      (Read JSON to Python, Deserialization, Decoding)


(2-1) 디스크에 있는 JSON 포맷 데이터를 json.load()를 사용하여 Python 객체로 읽어오기 (역직렬화, 디코딩 하기)

이어서, (1)번에서 with open(): json.dump() 로 만들어놓은 JSON 포맷의 데이터 "student_file.json" 를 Python 으로 역질렬화(deserialization)해서 읽어와 보겠습니다. with open("student_file.json", "r") 로 읽기 모드("r")로 JSON파일을 열어 후에, json.load(st_json)으로 디코딩하였습니다. 

import json

with open("student_file.json", "r") as st_json:

    st_python = json.load(st_json)


st_python

{'1.FirstName': 'Gildong',
 '2.LastName': 'Hong',
 '3.Age': 20,
 '4.University': 'Yonsei University',
 '5.Courses': [{'Classes': ['Probability',
    'Generalized Linear Model',
    'Categorical Data Analysis'],
   'Major': 'Statistics'},
  {'Classes': ['Data Structure', 'Programming', 'Algorithms'],
   'Minor': 'ComputerScience'}]}


이때 json.loads() 처럼 's'를 붙이면 TypeError: the JSON object must be str, not 'TextIOWrapper'가 발생합니다. (json.loads()가 아니라 json.load() 를 사용해야 함)

# use json.load() instead of json.loads()

with open("student_json_file.json", "r") as st_json:

    st_python = json.loads(st_json)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-c39634419df6> in <module>()
      1 with open("student_json_file.json", "r") as st_json:
----> 2     st_python = json.loads(st_json)

C:\Users\admin\Anaconda3\lib\json\__init__.py in loads(s, encoding, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    310     if not isinstance(s, str):
    311         raise TypeError('the JSON object must be str, not {!r}'.format(
--> 312                             s.__class__.__name__))
    313     if s.startswith(u'\ufeff'):
    314         raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)",

TypeError: the JSON object must be str, not 'TextIOWrapper'



(2-2) 메모리에 있는 JSON 포맷 데이터를 json.loads()로 Python 객체로 읽기 (역직렬화, 디코딩하기)

import json

st_python2 = json.loads(st_json3)

st_python2

{'1.FirstName': 'Gildong',
 '2.LastName': 'Hong',
 '3.Age': 20,
 '4.University': 'Yonsei University',
 '5.Courses': [{'Classes': ['Probability',
    'Generalized Linear Model',
    'Categorical Data Analysis'],
   'Major': 'Statistics'},
  {'Classes': ['Data Structure', 'Programming', 'Algorithms'],
   'Minor': 'ComputerScience'}]}


이때 만약 json.loads() 대신에 's'를 빼고 json.load()를 사용하면 AttributeError: 'str' object has no attribute 'read' 가 발생하니 주의하기 바랍니다. 

# use json.loads() instead of json.load()

st_python2 = json.load(st_json3)

---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)
<ipython-input-54-9de49903fef6> in <module>()
----> 1 st_python2 = json.load(st_json3)

C:\Users\admin\Anaconda3\lib\json\__init__.py in load(fp, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    263 
    264     """
--> 265     return loads(fp.read(),
    266         cls=cls, object_hook=object_hook,
    267         parse_float=parse_float, parse_int=parse_int,

AttributeError: 'str' object has no attribute 'read'


많은 도움이 되었기를 바랍니다. 

다음번 포스팅에서는 '웹(API)으로 부터 JSON 포맷 자료를 Python으로 읽어와서 pandas DataFrame으로 만드는 방법(https://rfriend.tistory.com/475)을 소개하겠습니다. 

Python으로 XML 파일 읽기, 쓰기는 https://rfriend.tistory.com/477 를 참고하세요. 

Python으로 YAML 파일 읽기, 쓰기는 https://rfriend.tistory.com/540 를 참고하세요. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요.  :-)


728x90
반응형
Posted by Rfriend
,

이전 포스팅에서 사전 자료형(dictionary data type)을 만드는 방법과 기본 사용법(https://rfriend.tistory.com/333), 사전 자료형 내장함수와 메소드(https://rfriend.tistory.com/334)에 대해서 설명한 적이 있습니다. 


사전 자료형을 중괄호로 묶여서 {키(key) : 값(value)} 의 쌍으로 이루어진다고 했으며, hash table type 으로 키를 해싱해놓고 있다가, 키를 기준으로 값을 찾으려고 하면 매우 빠른 속도로 찾아주는 효율적이고 편리한 자료형이라고 소개를 했었습니다. 


이번 포스팅에서는 사전 자료형을 


(1) 키를 기준으로 오름차순 정렬 (sort by key in ascending order)

(2) 키를 기준으로 내림차순 정렬 (sort by key in descending order)

(3) 값을 기준으로 오름차순 정렬 (sort by value in ascending order)

(4) 값을 기준으로 내림차순 정렬 (sort by value in descending order) 


하는 방법을 알아보겠습니다. 




{키 : 값} 쌍으로 이루어진 간단한 예제 사전 자료형을 만들어보겠습니다. (프로그래밍 언어별 인기도)


# make a dictionary

pgm_lang = {

    "java": 20, 

    "javascript": 8, 

    "c": 7,  

    "r": 4, 

    "python": 28 } 




 (1) 를 기준으로 오름차순 정렬 (sort by key in ascending order): sorted()


pgm_lang 사전형 자료를 키(key)를 기준으로 알파벳 오름차순으로 정렬해보겠습니다. dict.keys()를 정렬하면 keys 만 정렬된 값을 반환하며, dict.items()를 정렬하면 키(key)를 기준으로 정렬하되 키와 값을 튜플로 묶어서 정렬된 값을 반환합니다. 



sorted(pgm_lang.keys())


['c', 'java', 'javascript', 'python', 'r']



sorted(pgm_lang.items())


[('c', 7), ('java', 20), ('javascript', 8), ('python', 28), ('r', 4)]




그러면, 키를 기준으로 정렬한 (키, 값) 쌍의 튜플을 for loop 을 써서 한줄씩 인쇄를 해보겠습니다. 

첫번째 예는 일반적인 for loop이며, 두번째는 list comprehension 을 이용하여 같은 결과로 인쇄한 예입니다. 



for key, value in sorted(pgm_lang.items()):

    print(key, ":", value)


c : 7
java : 20
javascript : 8
python : 28
r : 4


# list comprehension

[print(key, ":", value) for (key, value) in sorted(pgm_lang.items())]


c : 7
java : 20
javascript : 8
python : 28
r : 4

[None, None, None, None, None]




키를 기준으로 정렬할 때, lambda 함수를 사용하여 새롭게 정의한 키(custom key function, logic)를 사용할 수도 있습니다. 아래 예는 키의 길이(length)를 기준으로 오름차순 정렬을 해본 것입니다. 



# sort a dictionary by custom key function (eg. by the length of key strings)

pgm_lang_len = sorted(pgm_lang.items(), key = lambda item: len(item[0])) # key: [0]


for key, value in pgm_lang_len:

    print(key, ":", value)


c : 7
r : 4
java : 20
python : 28
javascript : 8





 (2) 를 기준으로 내림차순 정렬 (sort by key in descending order): reverse=True


내림차순으로 정렬하려면 reverse=True 옵션을 추가해주면 됩니다. 



# sorting in reverse order

sorted(pgm_lang.keys(), reverse=True)


['r', 'python', 'javascript', 'java', 'c']



for (key, value) in sorted(pgm_lang.items(), reverse=True):

    print(key, ":", value)


r : 4
python : 28
javascript : 8
java : 20
c : 7





 (3) 을 기준으로 오름차순 정렬 (sort by value in ascending order)


값(value)을 기준으로 정렬하려면 앞서 소개했던 lambda 함수를 이용하여 키(key)로 사용할 기준이 값(value), 즉 item[1] 이라고 지정을 해주면 됩니다. (키는 item[0], 값은 item[1] 로 indexing)



sorted(pgm_lang.items(), key = lambda item: item[1]) # value: [1]


[('r', 4), ('c', 7), ('javascript', 8), ('java', 20), ('python', 28)]

 



값을 기준으로 오름차순 정렬한 결과를 for loop을 같이 사용하여 (키 : 값) 한쌍씩 차례대로 프린트를 해보겠습니다. (list comprehension 을 사용해도 결과 동일)



pgm_lang_val = sorted(pgm_lang.items(), key = lambda item: item[1])


for key, value in pgm_lang_val:

    print(key, ":", value)


r : 4
c : 7
javascript : 8
java : 20
python : 28


# list comprehension

[print(key, ":", value) for (key, value) in sorted(pgm_lang.items(), key = lambda item: item[1])]


r : 4
c : 7
javascript : 8
java : 20
python : 28
[None, None, None, None, None]





 (4) 을 기준으로 내림차순 정렬 (sort by value in descending order) 


위의 (3)번과 같이 값을 기준으로 정렬하므로 key=lambda x:x[1] 로 값(value, 즉 x[1])이 정렬 기준이라고 지정을 해주구요, 대신 내림차순이므로 reverse=True 옵션을 추가해주면 됩니다. 



# in reverse order

pgm_lang_val_reverse = sorted(pgm_lang.items()

                              reverse=True

                              key=lambda item: item[1])


for key, value in pgm_lang_val_reverse:

    print(key, ":", value)


python : 28
java : 20
javascript : 8
c : 7
r : 4




바로 위의 예에서는 for loop 순환할 때 key, value 2개 객체로 따로 따로 받았는데요, 바로 아래의 예처럼 items 로 (키, 값) 튜플로 받아서 items[0] 으로 키, items[1] 로 값을 indexing 할 수도 있습니다. 



# the same result with above

for items in pgm_lang_val_reverse:

    print(items[0], ":", items[1])


python : 28
java : 20
javascript : 8
c : 7
r : 4




이상으로 사전 자료형(dictionary)의 키, 값 기준 정렬(sorted)하는 4가지 방법 소개를 마치겠습니다. 



많은 도움이 되었기를 바랍니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 구분자(delimiter, separator)를 포함한 문자열 칼럼을 구분자를 기준으로 여러개의 칼럼으로 나누어서 DataFrame을 만드는 방법을 소개하겠습니다. 


그리고 PoestgreSQL, Greenplum DB에서도 구분자를 포함한 칼럼을 구분자를 기준으로 여러개의 칼럼으로 나누는 방법도 이어서 소개하겠습니다. 






 (1) pandas DataFrame 내 문자열 칼럼을 구분자로 분리하여 여러개의 칼럼 만들기


먼저 간단한 예를 들기 위해 ':' 구분자(delimiter, separator)를 가진 'col' 이라는 이름의 칼럼을 가진 pandas DataFrame을 만들어보겠습니다. 


import pandas as pd


df = pd.DataFrame({'col': ['a:1:20.3:S', 'b:2:10.5:C', 'c:3:51.9:A']})


df

col
0a:1:20.3:S
1b:2:10.5:C
2c:3:51.9:A




이제 원래의 'df' 라는 이름의 DataFrame 에 'col'변수를 그대로 둔 채로, ':' 구분자를 기준으로 'col' 문자열 칼럼을 분리(split) 하여 'group', 'id', 'value', 'grade' 라는 새로운 4개의 칼럼을 생성하여 추가해보겠습니다. split() 문자열 메소드는 split(separator, maxsplit) 의 형식으로 사용합니다. 



df[['group', 'id', 'value', 'grade']] = pd.DataFrame(df.col.str.split(':', 3).tolist())


df

colgroupidvaluegrade
0a:1:20.3:Sa120.3S
1b:2:10.5:Cb210.5C
2c:3:51.9:Ac351.9A

 



원래의 'col' 이름의 칼럼이 필요 없을 경우 원래의 DataFrame을 덮어쓰거나, 아니면 'col'을 포함하지 않는 새로운 DataFrame을 만들어주면 됩니다. 



df2 = pd.DataFrame(df.col.str.split(':', 3).tolist()

                   columns = ['group', 'id', 'value', 'grade'])


df2

groupidvaluegrade
0a120.3S
1b210.5C
2c351.9A

 



문자열(string)을 분리(split)해서 만든 새로운 칼럼들은 전부 문자열(string) 데이터 형식입니다. 이중에서 'id'와 'value' 칼럼을 숫자형(numeric)으로 변경하는 방법은 https://rfriend.tistory.com/470 포스팅을 참고하세요. 



df2.dtypes

group    object
id       object
value    object
grade    object
dtype: object

 





 (2) PostgreSQL, GPDB에서 문자열 칼럼을 구분자로 분리하여 여러개 칼럼 만들기


PostgreSQL, Greenplum DB에서는 split_part(string_column, separator, field_number) 의 형식으로 문자열 칼럼을 나눌 수 있습니다. 




위의 Python pandas DataFrame에서 사용했던 것과 동일한 예제 Table을 만들어서, 'col' 문자열 칼럼을 'group', 'id', value', 'grade'의 4개의 문자열(string)을 가진 새로운 Table을 만들어보겠습니다. 


-- make a table

DROP TABLE IF EXISTS grp_val_grade;

CREATE TABLE grp_val_grade (

col varchar(100) NOT NULL

);


INSERT INTO grp_val_grade VALUES ('a:1:20.3:S');

INSERT INTO grp_val_grade VALUES('b:2:10.5:C');

INSERT INTO grp_val_grade VALUES('c:3:51.9:A');


SELECT * FROM grp_val_grade; 





다음으로 split_part(string_column, separator, field_number) 함수를 사용해서 문자열 칼럼을 ':' 구분자를 기준으로 나누어서 새로운 칼럼을 만들어보겠습니다. 



-- split a column by delimeter and make 4 columns

DROP TABLE IF EXISTS grp_val_grade2;

CREATE TABLE grp_val_grade2 AS (

SELECT 

col

, split_part(col, ':', 1) AS group

, split_part(col, ':', 2) AS id 

, split_part(col, ':', 3) AS value

, split_part(col, ':', 4) AS grade

FROM grp_val_grade

);


SELECT * FROM grp_val_grade2;




많은 도움이 되었기를 바랍니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)



728x90
반응형
Posted by Rfriend
,