이전 포스팅에서는 무작위(확률, 임의) 표본 추출과 관련하여,

- numpy.random() 메소드를 이용하여 확률분포별 확률 표본 추출, 난수 생성: https://rfriend.tistory.com/284

- 그룹별 무작위 표본 추출: https://rfriend.tistory.com/407

- 기계학습을 위한 Train, Test 데이터셋 분할: https://rfriend.tistory.com/519

- 층화 무작위 추출을 통한 Train, Test 데이터셋 분할: https://rfriend.tistory.com/520

방법에 대하여 소개하였습니다.



이번 포스팅에서는 Python pandas 모듈의 DataFrame.sample() 메소드를 사용해서 DataFrame으로 부터 무작위 (확률, 임의) 표본 추출 (random sampling) 하는 방법을 소개하겠습니다.


(1) DataFrame으로 부터 특정 개수의 표본을 무작위로 추출하기 (number)

(2) DataFrame으로 부터 특정 비율의 표본을 무작위로 추출하기 (fraction)

(3) DataFrame으로 부터 복원 무작위 표본 추출하기 (random sampling with replacement)

(4) DataFrame으로 부터 가중치를 부여하여 표본 추출하기 (weights)

(5) DataFrame으로 부터 칼럼에 대해 무작위 표본 추출하기 (axis=1, axis='column)

(6) DataFrame으로 부터 특정 칼럼에 대해 무작위 표본 추출한 결과를 numpy array로 할당하기



[ pandas DataFrame에서 무작위 (확률) 표본 추출하기: pandas.DataFrame.sample() ]



  (1) DataFrame으로 부터 특정 개수의 표본을 무작위(확률)로 추출하기 (number)


예제로 사용할 4개의 관측치와 3개의 칼럼을 가진 pandas DataFrame을 만들어보겠습니다.

(참조 [1] 의 pandas tutorial 코드 사용하였습니다.)



import pandas as pd

df = pd.DataFrame({'num_legs': [2, 4, 8, 0],
                   'num_wings': [2, 0, 0, 0],
                   'num_specimen_seen': [10, 2, 1, 8]},
                  index=['falcon', 'dog', 'spider', 'fish'])

df


num_legsnum_wingsnum_specimen_seen
falcon2210
dog402
spider801
fish008

 



DataFrame.sample() 메소드의 n 매개변수를 사용해서 특정 개수 (number)의 표본을 무작위로 추출할 수 있습니다. 그리고 random_state 매개변수는 무작위(확률) 표본 추출을 위한 난수(random number)를 생성할 때 초기값(seed number) 로서, 재현가능성(reproducibility)을 위해서 설정해줍니다.


아래 예에서는 총 4개 관측치 중에서 2개의 관측치 (n=2) 를 무작위 표본 추출해보았습니다. Index를 기준으로 n 개수 만큼 표본을 추출해서 모든 칼럼의 값을 pandas DataFrame 자료구조로 반환합니다.



df.sample(n=2, # number of items from axis to return.
          random_state=1004) # seed for random number generator for reproducibility



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 




  (2) DataFrame으로 부터 특정 비율의 표본을 무작위로 추출하기 (fraction)


DataFrame으로 부터 특정 비율(fraction)으로 무작위 표본 추출을 하고 싶으면 frac 매개변수에 0~1 사이의 부동소수형(float) 값을 입력해주면 됩니다.



df.sample(frac=0.5, # fraction of axis items to return.
          random_state=1004)



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 



만약 비복원 추출 모드 (replace = False, 기본 설정) 에서 frac 값이 1을 초과할 경우에는 "ValueError: Replace has to be set to 'True' when upsampling the population 'frac' > 1." 이라는 에러가 발생합니다. 왜냐하면 모집단의 표본 개수 (100%, frac=1) 보다 더 많은 표본을 비복원 추출로는 할 수 없기 때문입니다. (복원 추출의 경우 동일한 관측치를 다시 표본 추출할 수 있으므로 frac > 1 인 경우도 가능함.)



## ValueError: Replace has to be set to `True` when upsampling the population `frac` > 1.
df.sample(frac=1.5,
          random_state=1004)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-45-2fcc4494d7ae> in <module>
----> 1 df.sample(frac=1.5, # fraction of axis items to return. 
      2           random_state=1004)

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/generic.py in sample(self, n, frac, replace, weights, random_state, axis)
   5326             n = 1
   5327         elif frac is not None and frac > 1 and not replace:
-> 5328             raise ValueError(
   5329                 "Replace has to be set to `True` when "
   5330                 "upsampling the population `frac` > 1."

ValueError: Replace has to be set to `True` when upsampling the population `frac` > 1.

 



만약 DataFrame.sample() 메소드에서 표본 개수 n 과 표본추출 비율 frac 을 동시에 설정하게 되면 "ValueError: Please enter a value for 'frac' OR 'n', not both" 에러가 발생합니다. n 과 frac 둘 중에 하나만 입력해야 합니다.



## parameter 'n' and 'frac' cannot be used at the same time.
## ValueError: Please enter a value for `frac` OR `n`, not both
df.sample(n=2, frac=0.5)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-b31ebc150882> in <module>
      1 ## parameter 'n' and 'frac' cannot be used at the same time.
      2 ## ValueError: Please enter a value for `frac` OR `n`, not both
----> 3 df.sample(n=2, frac=0.5)

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/generic.py in sample(self, n, frac, replace, weights, random_state, axis)
   5335             n = int(round(frac * axis_length))
   5336         elif n is not None and frac is not None:
-> 5337             raise ValueError("Please enter a value for `frac` OR `n`, not both")
   5338 
   5339         # Check for negative sizes

ValueError: Please enter a value for `frac` OR `n`, not both

 




  (3) DataFrame으로 부터 복원 무작위 표본 추출하기

      (random sampling with replacement)


한번 추출한 표본을 다시 모집단에 되돌려 넣고 추출하는 방법을 복원 추출법 (sampling with replacement) 이라고 합니다. 복원 추출법을 사용하면 동일한 표본이 중복해서 나올 수 있습니다.


DataFrame.sample() 메소드에서는 repalce=True 로 설정하면 복원 추출을 할 수 있습니다. 많은 경우 한번 추출된 표본은 되돌려 놓지 않고 표본을 추출하는 비복원 추출(sampling without replacement)을 사용하며, 기본 설정은 replace=False 입니다.



## replace=True: random sampling with replacement
df.sample(n=8, # or equivalently: frac=2
          replace=True, # random sampling with replacement
          random_state=1004)



num_legsnum_wingsnum_specimen_seen
spider801
fish008
fish008
dog402
fish008
fish008
fish008
spider801

 



만약 비복원 추출 모드 (replace=False) 에서 원본 DataFrame 의 관측치 개수 (행의 개수) 보다 많은 수의 표본을 무작위 추출하고자 한다면 "ValueError: Cannot take a larger sample than population when 'replace=False'" 에러 메시지가 발생합니다.  모집단이 가지고 있는 관측치 수보다 더 많은 수의 표본을 중복이 없는 "비복원 추출"로는 불가능하기 때문입니다.

(복원추출(sampling with replacement, replace=True) 모드 에서는 동일한 표본을 중복 추출이 가능하므로 모집단 관측치 수보다 많은 수의 표본 추출이 가능함.)



## ValueError: Cannot take a larger sample than population when 'replace=False'
df.sample(n=8,
          replace=False # random sampling without replacement
)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-42-40c76bd4c271> in <module>
      1 ## replace=True: random sampling with replacement
----> 2 df.sample(n=8, # or equivalently: frac=2
      3           replace=False # random sampling without replacement
      4 )

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/generic.py in sample(self, n, frac, replace, weights, random_state, axis)
   5343             )
   5344 
-> 5345         locs = rs.choice(axis_length, size=n, replace=replace, p=weights)
   5346         return self.take(locs, axis=axis)
   5347 

mtrand.pyx in numpy.random.mtrand.RandomState.choice()

ValueError: Cannot take a larger sample than population when 'replace=False'

 




  (4) DataFrame으로 부터 가중치를 부여하여 표본 추출하기 (weights)


만약에 DataFrame 내의 특정 칼럼의 값을 기준으로 가중치를 부여하여 무작위 표본 추출을 하고 싶다면 DataFrame.sample() 메소드의 weights 매개변수에 가중치로 사용할 칼럼 이름을 설정해주면 됩니다.


아래 예에서는 df DataFrame의 'num_specimen_seen' 칼럼의 값이 크면 클수록 표본으로 뽑힐 확률이 더 크도록 가중치(weights)를 부여해보았습니다. 아니나 다를까, 'num_specimen_seen' 값이 10, 8 인 falcon, fish가 표본으로 추출이 되었네요. 

(물론, 표본추출 시행을 계속 하다보면 num_specimen_seen 값이 1인 spider나 2인 dog 도 표본으로 뽑히는 때가 오긴 올겁니다. 다만, num_specimen_seen 값의 가중치로 인해 표본 추출될 확률이 낮아 상대적으로 작은 빈도로 추출이 되겠지요.)



## Using a DataFrame column as weights.
## Rows with larger value in the num_specimen_seen column are more likely to be sampled.
df.sample(n=2,
          weights='num_specimen_seen'

)



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 




  (5) DataFrame으로 부터 칼럼에 대해 무작위 표본 추출하기 (axis=1, axis='column)


위의 (1) ~ (4) 까지는 axis=0, 즉 Index 에 대해서 무작위 표본 추출을 해서 전체 칼럼의 값을 반환하였습니다.


DataFrame.sample() 메소드의 axis 매개변수를 axis=1, 또는 axis='column' 으로 설정을 해주면 여러개의 칼럼에 대해서 무작위로 표본 추출을 해서 전체 행(all rows, random sampled columns) 을 반환합니다. (이런 요건의 분석은 그리 많지는 않을것 같습니다만, 이런 기능도 있다는 정도로만 알아두면 되겠습니다.)



## Axis to sample: by column
df.sample(n=2,
          random_state=1004,
          axis=1) # or equivalently, axis='column'



num_legsnum_wings
falcon22
dog40
spider80
fish00

 



axis 매개변수의 기본 설정은 대부분의 분석 요건에 해당하는 Index 기준의 무작위 표본 추출인 axis=0 (or, axis='index') 입니다.



## Axis to sample: by index
df.sample(n=2,
          random_state=1004,
          axis=0) # or equivalently, axis='index', default



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 




  (6) DataFrame으로 부터 특정 칼럼에 대해 무작위 표본 추출한 결과를

       numpy array로 할당하기


만약 DataFrame의 여러개의 칼럼 중에서 특정 하나의 칼럼에 대해서만 무작위 표본 추출을 하고 싶다면 DataFrame['column_name'] 형식으로 먼저 Series 로 특정 칼럼의 값을 가져오고, 이에 대해서 sample() 메소드를 사용하면 됩니다.



## Sampling only for a column
df['num_legs'].sample(n=2, random_state=1004)


[Out] 
falcon 2 fish 0 Name: num_legs, dtype: int64

 



df['num_specimen_seen'].sample(n=2, random_state=1004)


[Out]
falcon 10 fish 8 Name: num_specimen_seen, dtype: int64

 



이렇게 DataFrame으로 부터 특정 하나의 칼럼 값을 Series 로 인덱싱해와서 무작위 표본 추출을 하면, 역시 그 결과 객체의 데이터 유형도 Series 입니다.



## Assigning sampling results as Series
samp_Series = df['num_legs'].sample(n=2)
type(samp_Series)


[Out] pandas.core.series.Series

 



만약, DataFrame으로 부터 특정 하나의 칼럼 값 Series 로 부터의 무작위 표본 추출 결과를 Numpy Array로 할당해서 결과를 가져오고 싶다면 numpy.array() 로 Series 를 array 로 변환해주면 됩니다.



## Assigning sampling results as numpy array
import numpy as np
samp_array = np.array(df['num_legs'].sample(n=2))
type(samp_array)

[Out] numpy.ndarray


samp_array

[Out] array([0, 2])




[ Reference ]

* pandas.DataFrame.sample: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html



이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요!  :-)




728x90
Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 pandas 모듈의 DataFrame.iterrows(),  DataFrame.iteritems(), DataFrame.itertuples() 의 메소드 3총사와 for loop 반복문 활용하여 pandas DataFrame 자료의 행, 열, (행, 열) 튜플에 대해서 순환 반복 (for loop iteration) 하여 자료를 반환하는 방법을 소개하겠습니다.


(1) pd.DataFrame.iterrows() : 행에 대해 순환 반복
    (Iterate over DataFrame rows as (index, Series) pairs.)

(2) pd.DataFrame.iteritems() : 열에 대해 순환 반복
    (Iterate over DataFrame (column name, Series) pairs.)

(3) pd.DataFrame.itertuples() : 이름이 있는 튜플 (인덱스, 행, 열) 에 대해 순환 반복

    (Iterate over DataFrame rows as namedtuples)



[ Pandas DataFrame의 행, 열, (행, 열) 튜플 순환 반복 ]





  (1) DataFrame.iterrows() : 행에 대해 순환 반복
      (Iterate over DataFrame rows as (index, Series) pairs.)


먼저 pandas 모듈을 importing 하고, 예제로 사용할 2개의 칼럼과 인덱스를 가진 간단한 DataFrame을 만들어보겠습니다.



import pandas as pd


df = pd.DataFrame(
    {'price': [100, 200, 300],
     'weight': [20.3, 15.1, 25.9]},
    index=['idx_a', 'idx_b', 'idx_c'])

df


priceweight
idx_a10020.3
idx_b20015.1
idx_c30025.9




이제 DataFrame.iterrows() 메소드와 for loop 반복문을 사용해서 행(row)에 대해서 순환하면서 인덱스 이름과 각 행별 칼럼별 데이터를 출력해보겠습니다.



## DataFrame.iterrows()
for idx, row in df.iterrows():
    print("** index name:", idx)
    print(row)
    print("------"*5)


[Out]
** index name: idx_a price 100.0 weight 20.3 Name: idx_a, dtype: float64 ------------------------------ ** index name: idx_b price 200.0 weight 15.1 Name: idx_b, dtype: float64 ------------------------------ ** index name: idx_c price 300.0 weight 25.9 Name: idx_c, dtype: float64 ------------------------------



DataFrame에 여러개의 칼럼이 있고, 이중에서 특정 칼럼에 대해서만 행을 순회하면서 행별 특정 칼럼의 값을 반복해서 출력하고 싶으면 row['column_name'] 또는 row[position_int] 형식으로 특정 칼럼의 이름이나 위치 정수를 넣어주면 됩니다.



## accessing to column of each rows by indexing
for idx, row in df.iterrows():
    print(idx)
    print(row['price']) # or print(row[0])
    print("-----")


[Out]
idx_a 100.0 ----- idx_b 200.0 ----- idx_c 300.0 -----



DataFrame.iterrows() 메소드는 결과물로 (index, Series) 짝(pairs)을 반환합니다. 따라서 원본 DataFrame에서의 데이터 유형일 보존하지 못하므로 행별 Series 에서는 데이터 유형이 달라질 수 있습니다.


가령, 예제의 DataFrame에서 'price' 칼럼의 데이터 유형은 '정수형(integer64)' 인데 반해, df.iterrows() 로 반환된 'row['price']'의 데이터 유형은 '부동소수형(float64)'으로 바뀌었습니다.



## DataFrame.iterrows() returns a Series for each row,
## it does not preserve dtypes across the rows.
print('Data type of df price:', df['price'].dtype) # int
print('Data type of row price:', row['price'].dtype) # float


[Out]
Data type of df price: int64 Data type of row price: float64





  (2) DataFrame.iteritems() : 열에 대해 순환 반복
      (Iterate over DataFrame (column name, Series) pairs.)


위의 (1)번이 DataFrame의 행(row)에 대해 순환 반복을 했다면, 이번에는 pandas DataFrame의 열(column)에 대해 iteritems() 메소드와 for loop 문을 사용해 순환 반복(iteration) 하면서 '칼럼 이름 (column name)' 과 '행별 값 (Series for each row)' 을 짝으로 하여 출력해 보겠습니다.



df


priceweight
idx_a10020.3
idx_b20015.1
idx_c30025.9



for col, item in df.iteritems():
    print("** column name:", col)
    print(item) # = print(item, sep='\n')
    print("-----"*5)


[Out]
** column name: price idx_a 100 idx_b 200 idx_c 300 Name: price, dtype: int64 ------------------------- ** column name: weight idx_a 20.3 idx_b 15.1 idx_c 25.9 Name: weight, dtype: float64 -------------------------




만약 DataFrame.iteritems() 와 for loop 문으로 열(column)에 대해 순환 반복하여 각 행(row)의 값을 출력하는 중에 특정 행만을 출력하고 싶으면 '행의 위치 정수(position index of row)'나 '행의 인덱스 이름 (index name of row)' 으로 item 에서 인덱싱해주면 됩니다.



for col, item in df.iteritems():
    print(col)
    print(item[0]) # = print(item['idx_a'])


[Out]
price 100 weight 20.3





  (3) DataFrame.itertuples() : 이름이 있는 튜플 (인덱스, 행, 열) 에 대해 순환 반복

    (Iterate over DataFrame rows as namedtuples)


위의 (1) 번의 DataFrame.iterrows() 에서는 DataFrame의 행(row)에 대해 순환 반복, (2) 번의 DataFrame.iteritems() 에서는 열(column, item)에 대해 순환 반복하였습니다. 반면에, 경우에 따라서는 (인덱스, 행, 열) 의 튜플 묶음 단위로 순환 반복을 하고 싶을 때 DataFrame.itertuples() 메소드를 사용할 수 있습니다.


각 행과 열에 대해서 순환 반복하면서 값을 가져오고, 이를 zip() 해서 묶어주는 번거로운 일을 DataFrame.itertuples() 메소드는 한번에 해주니 알아두면 매우 편리한 메소드입니다.


아래의 예는 DataFrame.itertuples() 메소드와 for loop 문을 사용해서 'df' DataFrame의 이름있는 튜플인 namedtuple (Index, row, column) 에 대해서 순환 반복하면서 출력을 해보겠습니다.



df


priceweight
idx_a10020.3
idx_b20015.1
idx_c30025.9



for row in df.itertuples():
    print(row)


[Out] 
Pandas(Index='idx_a', price=100, weight=20.3) Pandas(Index='idx_b', price=200, weight=15.1) Pandas(Index='idx_c', price=300, weight=25.9)



만약 인덱스를 포함하고 싶지 않다면 index=False 로 매개변수를 설정해주면 됩니다.



## By setting the indx=False, we can remove the index as the first element of the tuple.
for row in df.itertuples(index=False):
    print(row)


[Out] 
Pandas(price=100, weight=20.3) Pandas(price=200, weight=15.1) Pandas(price=300, weight=25.9)



DataFrame.itertuples() 메소드가 이름있는 튜플(namedtuples)을 반환한다고 했는데요, name 매개변수로 튜플의 이름을 부여할 수도 있습니다. 아래 예에서는 name='Product' 로 해서 튜플에 'Product'라는 이름을 부여해보았습니다.



## Setting a custom name for the yielded namedtuples.
for row in df.itertuples(name='Product'):
    print(row)


[Out]
Product(Index='idx_a', price=100, weight=20.3) Product(Index='idx_b', price=200, weight=15.1) Product(Index='idx_c', price=300, weight=25.9)



DataFrame.iterrows() 는 (index, Series) 짝을 반환하다보니 원본 DataFrame의 데이터 유형을 보존하지 못한다고 했는데요, DataFrame.itertuples() 는 원본 DataFrame의 데이터 유형을 그대로 보존합니다.


아래 예에서 볼 수 있듯이 df['price']의 데이터 유형과 df.itertuples()의 결과의 row.price 의 데이터 유형이 둘 다 '정수(int64)'로 동일합니다.



## DataFrame.itertuples() preserves dtypes, returning namedtuples of the values.
print('Data type of df price:', df['price'].dtype) # int
print('Data type of row price:', type(row.price)) # int


[Out] 
Data type of df price: int64 Data type of row price: <class 'int'>



[Reference]

* DataFrame.iterrows(): https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html#pandas.DataFrame.iterrows

* DataFrame.iteritems(): https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iteritems.html

* DataFrame.itertuples(): https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.itertuples.html#pandas.DataFrame.itertuples


이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요! :-)




728x90
Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 (1) 날짜 TimeStamp (m개) 와 (2) 고객 ID (n개) 의 두 개 칼럼을 가진 DataFrame에서 고객ID 별로 중간 중간 날짜 TimeStamp 가 비어있는 경우 모두 '0'으로 채워서 모든 조합 (m * n 개) 을 MultiIndex로 가진 시계열 데이터 형태의 DataFrame을 만들어보겠습니다. 


이번 포스팅에서는 pandas DataFrame에서 index 설정 관련해서 MultiIndex(), set_index(), reindex(), reset_index() 등 index 설정과 재설정 관련된 여러가지 메소드가 나옵니다. 


말로만 설명을 들으면 좀 어려운데요, 아래의 전(Before) --> 후(After) DataFrame의 전처리 Output Image를 보면 이해하기가 쉽겠네요. (시계열 데이터 분석을 할 때 아래의 우측에 있는 것처럼 데이터 전처리를 미리 해놓아야 합니다.) 




먼저, 년-월-일 TimeStamp (ts), 고객 ID (id), 구매 금액 (amt)의 세개 칼럼으로 구성된 거래 데이터(tr)인, 예제로 사용할 간단한 pandas DataFrame을 만들어보겠습니다. 



import pandas as pd


tr = pd.DataFrame({

    'ts': ['2020-06-01', '2020-06-02', '2020-06-03', '2020-06-01', '2020-06-03'], 

    'id': [1, 1, 1, 2, 3], 

    'amt': [100, 300, 50, 200, 150]})


tr

tsidamt
02020-06-011100
12020-06-021300
22020-06-03150
32020-06-012200
42020-06-033150

 



다음으로, 거래 데이터(tr) DataFrame의 날짜(ts)와 고객ID(id)의 모든 조합(all combination)으로 구성된  Multi-Index 를 만들어보겠습니다. pd.MultiIndex.from_product((A, B)) 메소드를 사용하면 Cartesian Product 을 수행하여, 총 A의 구성원소 개수 * B의 구성원소 개수 종류 만큼의 MultiIndex 를 생성해줍니다. 위 예제의 경우 날짜(ts)에 '2020-06-01', '2020-06-02', '2020-06-03'의 3개 날짜가 있고, 고객ID(id) 에는 1, 2, 3 의 3개가 있으므로 Cartesian Product 을 하면 아래의 결과처럼 3 * 3 = 9 의 조합이 생성이 됩니다. 



date_id_idx = pd.MultiIndex.from_product((set(tr.ts), set(tr.id)))

date_id_idx

MultiIndex([('2020-06-01', 1),
            ('2020-06-01', 2),
            ('2020-06-01', 3),
            ('2020-06-02', 1),
            ('2020-06-02', 2),
            ('2020-06-02', 3),
            ('2020-06-03', 1),
            ('2020-06-03', 2),
            ('2020-06-03', 3)],
           )



이제 위에서 Cartesian Product으로 만든 TimeStamp(ts)와 고객ID(id)의 모든 조합으로 구성된 MultiIndex인 date_id_idx 를 사용하여 index를 재설정(reindex) 해보겠습니다. 이때 원래(Before)의 DataFrame에는 없었다가 date_id_idx 로 index를 재설정(reindex) 하면서 새로 생긴 행에 구매금액(amt) 칼럼에는 'NaN' 의 결측값이 들어가게 됩니다. 이로서 처음에 5개 행이었던 것이 이제 9개(3*3=9) 행으로 늘어났습니다. 



tr_tsformat = tr.set_index(['ts', 'id']).reindex(date_id_idx)

tr_tsformat

amt
2020-06-011100.0
2200.0
3NaN
2020-06-021300.0
2NaN
3NaN
2020-06-03150.0
2NaN
3150.0

 



날짜(ts)와 고객ID(id)의 MultiIndex로 reindex() 하면서 생긴 NaN 값을 '0'으로 채워넣기(fill_value=0)해서 새로 DataFrame을 만들어보겠습니다. 



tr_tsformat = tr.set_index(['ts', 'id']).reindex(date_id_idx, fill_value=0)

tr_tsformat

amt
2020-06-011100
2200
30
2020-06-021300
20
30
2020-06-03150
20
3150

 



만약 날짜(ts)와 고객ID(id)의 MultiIndex로 이루어진 위의 DataFrame에서 MultiIndex를 칼럼으로 변경하고 싶다면 reset_index() 함수를 사용하면 됩니다. 칼럼 이름은 애초의 DataFrame과 동일하게 ['ts', 'id', 'amt'] 로 다시 부여해주었습니다. 



tr_tsformat.reset_index(inplace=True)

tr_tsformat

level_0level_1amt
02020-06-011100
12020-06-012200
22020-06-0130
32020-06-021300
42020-06-0220
52020-06-0230
62020-06-03150
72020-06-0320
82020-06-033

150

 


tr_tsformat.columns = ['ts', 'id', 'amt']

tr_tsformat

tsidamt
02020-06-011100
12020-06-012200
22020-06-0130
32020-06-021300
42020-06-0220
52020-06-0230
62020-06-03150
72020-06-0320
82020-06-033150




참고로, pandas에서 ID는 없이 TimeStamp만 있는 일정한 주기의 시계열 데이터 Series, DataFrame 만들기는 https://rfriend.tistory.com/501 를 참고하세요. 



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

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


728x90
Posted by R Friend Rfriend

댓글을 달아 주세요

지난번 포스팅에서는 Python pandas의 Series, DataFrame에서 시계열 데이터 index 의 중복 확인 및 처리하는 방법(https://rfriend.tistory.com/500) 에 대해서 소개하였습니다. 


이번 포스팅에서는 Python pandas에서 일정한 주기의 시계열 데이터(Fixed frequency time series)를 가진 Series, DataFrame 만드는 방법을 소개하겠습니다. 



[ 시계열 데이터의 특징 ]

  • 동일한/ 고정된 간격의 날짜-시간 index (equally spaced time interval, fixed frequency)
  • 중복 없고, 빠진 것도 없는 날짜-시간 index (no redundant values or gaps)
  • 시간 순서대로 정렬 (sequential order)

(* 시계열 데이터가 반드시 동일한/고정된 간격의 날짜-시간을 가져야만 하는 것은 아님. 가령, 주가(stock price) 데이는 장이 열리는 business day에만 존재하며 공휴일은 데이터 없음)





  (1) 동일 간격의 시계열 데이터 Series 만들기 (fixed frequency time series pandas Series)



(1-1) 중간에 날짜가 비어있는 시계열 데이터 Series 만들기 (non-equally spaced time series)


먼저, 예제로 사용할 간단한 시계열 데이터 pandas Series 를 만들어보겠습니다. 의도적으로 '2019-12-04', '2019-12-08' 일의 날짜-시간 index 를 제거(drop)하여 이빨 빠진 날짜-시간 index 를 만들었습니다. 



import pandas as pd


# generate dates from 2019-12-01 to 2019-12-10

date_idx = pd.date_range('2019-12-01', periods=10)

date_idx

[Out]:

DatetimeIndex(['2019-12-01', '2019-12-02', '2019-12-03', '2019-12-04', '2019-12-05', '2019-12-06', '2019-12-07', '2019-12-08', '2019-12-09', '2019-12-10'], dtype='datetime64[ns]', freq='D')


# drop 2 dates from DatetimeIndex

date_idx = date_idx.drop(pd.DatetimeIndex(['2019-12-04', '2019-12-08']))

date_idx

[Out]:
DatetimeIndex(['2019-12-01', '2019-12-02', '2019-12-03', '2019-12-05',
               '2019-12-06', '2019-12-07', '2019-12-09', '2019-12-10'],
              dtype='datetime64[ns]', freq=None)

# Time Series with missing dates index

series_ts_missing = pd.Series(range(len(date_idx))

                              , index=date_idx)


series_ts_missing

[Out]:
2019-12-01    0
2019-12-02    1
2019-12-03    2
2019-12-05    3
2019-12-06    4
2019-12-07    5
2019-12-09    6
2019-12-10    7
dtype: int64





(1-2) 이빨 빠진 Time Series를 동일한 간격의 시계열 데이터 pandas Series로 변환하기 

       (fixed frequency, equally spaced time interval time series)


위의 (1-1)에서 만든 Series는 '2019-12-04', '2019-12-08'일의 날짜-시간 index가 빠져있는데요, 이럴 경우 resample('D')를 이용하여 날짜-시간 index는 등간격의 날짜-시간을 채워넣고, 대신 값은 결측값 처리(missing value, NaN, Not a Number)를 해보겠습니다. 



# Create a 1 day Fixed Frequency Time Series using resample('D')

series_ts_fixed_freq = series_ts_missing.resample('D')

series_ts_fixed_freq.first()

[Out]:
2019-12-01    0.0
2019-12-02    1.0
2019-12-03    2.0
2019-12-04    NaN <---
2019-12-05    3.0
2019-12-06    4.0
2019-12-07    5.0
2019-12-08    NaN <---
2019-12-09    6.0
2019-12-10    7.0
Freq: D, dtype: float64




비어있던 '날짜-시간' index 를 등간격 '날짜-시간' index로 채우면서 값(value)에 'NaN'이 생긴 부분을 fillna(0)을 이용하여 '0'으로 채워보겠습니다. 



# fill missing value with '0'

series_ts_fixed_freq.first().fillna(0)

[Out]:
2019-12-01    0.0
2019-12-02    1.0
2019-12-03    2.0
2019-12-04    0.0 <---
2019-12-05    3.0
2019-12-06    4.0
2019-12-07    5.0
2019-12-08    0.0 <---
2019-12-09    6.0
2019-12-10    7.0
Freq: D, dtype: float64

 




이번에는 resample('10T')를 이용하여 '10분 단위의 동일 간격 날짜-시간' index의 시계열 데이터를 만들어보겠습니다. 이때도 원래의 데이터셋에 없던 '날짜-시간' index의 경우 값(value)은 결측값으로 처리되어 'NaN'으로 채워집니다. 



# resampling with 10 minutes frequency (interval)

series_ts_missing.resample('10T').first()

[Out]:

2019-12-01 00:00:00 0.0 2019-12-01 00:10:00 NaN 2019-12-01 00:20:00 NaN 2019-12-01 00:30:00 NaN 2019-12-01 00:40:00 NaN ... 2019-12-09 23:20:00 NaN 2019-12-09 23:30:00 NaN 2019-12-09 23:40:00 NaN 2019-12-09 23:50:00 NaN 2019-12-10 00:00:00 7.0 Freq: 10T, Length: 1297, dtype: float64

 





  (2) 동일 간격의 시계열 데이터 DataFrame 만들기 

       (fixed frequency time series pandas DataFrame)



(2-1) 중간에 날짜가 비어있는 시계열 데이터 DataFrame 만들기 (non-equally spaced time series DataFrame)


pd.date_range() 함수로 등간격의 10일치 날짜-시간 index를 만든 후에, drop(pd.DatetimeIndex()) 로 '2019-12-04', '2019-12-08'일을 제거하여 '이빨 빠진 날짜-시간' index를 만들었습니다. 



import pandas as pd


# generate dates from 2019-12-01 to 2019-12-10

date_idx = pd.date_range('2019-12-01', periods=10)


# drop 2 dates from DatetimeIndex

date_idx = date_idx.drop(pd.DatetimeIndex(['2019-12-04', '2019-12-08']))

date_idx

[Out]:

DatetimeIndex(['2019-12-01', '2019-12-02', '2019-12-03', '2019-12-05',

'2019-12-06', '2019-12-07', '2019-12-09', '2019-12-10'], dtype='datetime64[ns]', freq=None)


df_ts_missing = pd.DataFrame(range(len(date_idx))

                             , columns=['col']

                             , index=date_idx)


df_ts_missing

[Out]:

col
2019-12-010
2019-12-021
2019-12-032
2019-12-053
2019-12-064
2019-12-075
2019-12-096
2019-12-107

 




(2-2) 이빨 빠진 Time Series를 동일한 간격의 시계열 데이터 pandas DataFrame으로 변환하기 

       (fixed frequency, equally spaced time interval time series pandas DataFrame)


resample('D') 를 메소드를 사용하여 '일(Day)' 동일 간격의 '날짜-시간' index를 가지는 시계열 데이터 DataFrame을 만들었습니다. 이때 원래의 데이터에 없던 '날짜-시간' index의 경우 결측값 처리되어 값(value)은 'NaN'으로 처리됩니다. 



df_ts_fixed_freq = df_ts_missing.resample('D').first()

df_ts_fixed_freq

[Out]:

col
2019-12-010.0
2019-12-021.0
2019-12-032.0
2019-12-04NaN <---
2019-12-053.0
2019-12-064.0
2019-12-075.0
2019-12-08NaN <---
2019-12-096.0
2019-12-107.0




동일 간견 시계열 데이터로 변환하는 과정에서 생긴 'NaN' 결측값 부분을 fillina(0) 메소드를 이용하여 '0'으로 대체하여 채워보겠습니다. 



# fill missing value with '0'

df_ts_fixed_freq = df_ts_fixed_freq.fillna(0)

df_ts_fixed_freq

col
2019-12-010.0
2019-12-021.0
2019-12-032.0
2019-12-040.0 <---
2019-12-053.0
2019-12-064.0
2019-12-075.0
2019-12-080.0 <---
2019-12-096.0
2019-12-107.0




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

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



728x90
Posted by R Friend Rfriend

댓글을 달아 주세요

지난번 포스팅에서는 날짜-시간 시계열 객체(date-time, Timeseries objects)를 문자열(Strings)로 변환하기, 거꾸로 문자열을 날짜-시간 시계열 객체로 변환하는 방법(https://rfriend.tistory.com/498)을 소개하였습니다. 


이번 포스팅에서는 날짜-시간 시계열 데이터(date-time time series) index로 가지는  Python pandas의 Series, DataFrame 에서 특정 날짜-시간을 indexing, slicing, selection, truncation 하는 방법을 소개하겠습니다. 


(1) pandas Series에서 시계열 데이터 indexing, slicing, selection, truncation 하는 방법

(2) pandas DataFrame에서 시계열 데이터 indexing, slicing, selection, truncation 하는 방법





  (1) pandas Series에서 시계열 데이터 indexing, slicing, selection, truncation 하는 방법


먼저, 간단한 예제로 사용하도록 2019년 11월 25일 부터 ~ 2019년 12월 4일까지 10일 기간의 년-월-일 날짜를 index로 가지는 pands Series를 만들어보겠습니다. 


pandas.date_range(시작날짜, periods=생성할 날짜-시간 개수)  함수를 사용하여 날짜-시간 데이터를 생성하였으며, 이를 index로 하여 pandas Series를 만들었습니다. 



import pandas as pd

from datetime import datetime


# DatetimeIndex

ts_days_idx = pd.date_range('2019-11-25', periods=10)

ts_days_idx

[Out]:

DatetimeIndex(['2019-11-25', '2019-11-26', '2019-11-27', '2019-11-28', '2019-11-29', '2019-11-30', '2019-12-01', '2019-12-02', '2019-12-03', '2019-12-04'], dtype='datetime64[ns]', freq='D')



# Series with time series index

series_ts = pd.Series(range(len(ts_days_idx))

                      , index=ts_days_idx)


series_ts

[Out]:
2019-11-25    0
2019-11-26    1
2019-11-27    2
2019-11-28    3
2019-11-29    4
2019-11-30    5
2019-12-01    6
2019-12-02    7
2019-12-03    8
2019-12-04    9
Freq: D, dtype: int64



series_ts.index

[Out]: 
DatetimeIndex(['2019-11-25', '2019-11-26', '2019-11-27', '2019-11-28',
               '2019-11-29', '2019-11-30', '2019-12-01', '2019-12-02',
               '2019-12-03', '2019-12-04'],
              dtype='datetime64[ns]', freq='D')


series_ts.index[6]

[Out]: Timestamp('2019-12-01 00:00:00', freq='D')




참고로, 아례의 예처럼 pd.date_range(start='시작 날짜-시간', end='끝 날짜-시간') 처럼 명시적으로 시작과 끝의 날짜-시간을 지정해주어도 위의 perieds를 사용한 예와 동일한 결과를 얻을 수 있습니다. 



import pandas as pd


pd.date_range(start='2019-11-25', end='2019-12-04')

[Out]:
DatetimeIndex(['2019-11-25', '2019-11-26', '2019-11-27', '2019-11-28',
               '2019-11-29', '2019-11-30', '2019-12-01', '2019-12-02',
               '2019-12-03', '2019-12-04'],
              dtype='datetime64[ns]', freq='D')




참고로 하나더 소개하자면요, pandas.date_range('시작날짜-시간', period=생성할 날짜-시간 개수, freq='주기 단위') 에서 freq 옵션을 통해서 'S' 1초 단위, '10S' 10초 단위, 'H' 1시간 단위, 'D' 1일 단위, 'M' 1달 단위(월 말일 기준), 'Y' 1년 단위 (년 말일 기준) 등으로 날짜-시간 시계열 데이터 생성 주기를 설정할 수 있습니다. 매우 편하지요?!


< 1초 단위로 날짜-시간 데이터 10개를 생성한 예 >


# 10 timeseries data points by Second(freq='S')

pd.date_range('2019-11-25 00:00:00', periods=10, freq='S')

[Out]: 
DatetimeIndex(['2019-11-25 00:00:00', '2019-11-25 00:00:01',
               '2019-11-25 00:00:02', '2019-11-25 00:00:03',
               '2019-11-25 00:00:04', '2019-11-25 00:00:05',
               '2019-11-25 00:00:06', '2019-11-25 00:00:07',
               '2019-11-25 00:00:08', '2019-11-25 00:00:09'],
              dtype='datetime64[ns]', freq='S')



< 10초 단위로 날짜-시간 데이터 10개를 생성한 예 >


# 10 timeseries data points by 10 Seconds (freq='10S')

pd.date_range('2019-11-25 00:00:00', periods=10, freq='10S')

[Out]:

DatetimeIndex(['2019-11-25 00:00:00', '2019-11-25 00:00:10', '2019-11-25 00:00:20', '2019-11-25 00:00:30', '2019-11-25 00:00:40', '2019-11-25 00:00:50', '2019-11-25 00:01:00', '2019-11-25 00:01:10', '2019-11-25 00:01:20', '2019-11-25 00:01:30'], dtype='datetime64[ns]', freq='10S')

 



(1-1) 시계열데이터를 index로 가지는 pandas Series에서 특정 날짜-시간 데이터 indexing 하기


먼저 위에서 생성한 series_ts 라는 이름의 시간 순서대로 정렬되어 있는 Series 에서 7번째에 위치한 '2019-12-01' 의 값 '6'을 indexing 해보겠습니다. 


(a), (b)와 같이 위치(position)를 가지고 인덱싱할 수 있습니다. 

또한, (c), (d)와 같이 날짜-시간 문자열(String)을 가지고도 인덱싱(indexing)을 할 수 있습니다. 

(e) 처럼 datetime.datetime(year, month, day) 객체를 사용해서도 인덱싱할 수 있습니다. 



import pandas as pd

from datetime import datetime


# (a) indexing with index number

series_ts[6]

[Out]: 6


# (b) indexing with index number using iloc

series_ts.iloc[6]

[Out]: 6


# (c) indexing with string ['year-month-day']

series_ts['2019-12-01']

[Out]: 6


# (d) indexing with string ['month/day/year']

series_ts['12/01/2019']

[Out]: 6


# (f) indexing with datetime.datetime(year, month, day)

series_ts[datetime(2019, 12, 1)]

[Out]: 6





(1-2) 시계열데이터를 index로 가지는 pandas Series에서 날짜-시간 데이터 Slicing 하기


아래는 '2019-12-01' 일 이후의 값을 모두 slicing 해오는 5가지 방법입니다. 

(a), (b)는 위치(position):위치(position)을 이용하여 날짜를 index로 가지는 Series를 slicing을 하였습니다. 

(c), (d)는 '년-월-일':'년-월-일' 혹은 '월/일/년':'월/일/년' 문자열(string)을 이용하여 slicing을 하였습니다. 

(e)는 datetime.datetime(년, 월, 일):datetime.datetime(년, 월, 일) 을 이용하여 slicing을 하였습니다. 



import pandas as pd

from datetime import datetime


# (a) slicing with position

series_ts[6:]

[Out]:
2019-12-01    6
2019-12-02    7
2019-12-03    8
2019-12-04    9
Freq: D, dtype: int64


# (b) slicing with position using iloc

series_ts.iloc[6:]

[Out]:
2019-12-01    6
2019-12-02    7
2019-12-03    8
2019-12-04    9
Freq: D, dtype: int64

# (c) slicing with string

series_ts['2019-12-01':'2019-12-10']

[Out]:
2019-12-01    6
2019-12-02    7
2019-12-03    8
2019-12-04    9
Freq: D, dtype: int64

# (d) slicing with string

series_ts['12/01/2019':'12/10/2019']

[Out]:
2019-12-01    6
2019-12-02    7
2019-12-03    8
2019-12-04    9
Freq: D, dtype: int64

# (e) slicing with datetime

series_ts[datetime(2019, 12, 1):datetime(2019, 12, 10)]

[Out]:

2019-12-01    6
2019-12-02    7
2019-12-03    8
2019-12-04    9
Freq: D, dtype: int64




(1-3) 시계열데이터를 index로 가지는 pandas Series 에서 날짜-시간 데이터 Selection 하기


'날짜-시간' 문자열(String)을 이용하여 특정 '년', '월'의 모든 데이터를 선택할 수도 있습니다. 꽤 편리하고 재미있는 기능입니다. 


< '2019'년 모든 데이터 선택하기 예 >


# selection with year string

series_ts['2019']

[Out]:

2019-11-25 0 2019-11-26 1 2019-11-27 2 2019-11-28 3 2019-11-29 4 2019-11-30 5 2019-12-01 6 2019-12-02 7 2019-12-03 8 2019-12-04 9 Freq: D, dtype: int64

 



< '2019년 12월' 모든 데이터 선택하기 예 >


# selection with year-month string

series_ts['2019-12']

[Out]:
2019-12-01    6
2019-12-02    7
2019-12-03    8
2019-12-04    9
Freq: D, dtype: int64

 




(1-4) 시계열 데이터를 index로 가지는 pandas Series에서 날짜-시간 데이터 잘라내기 (Truncate)


truncate() methods를 사용하면 잘라내기(truncation)를 할 수 있습니다. before, after 옵션으로 잘라내기하는 범위 기간을 설정할 수 있는데요, 해당 날짜 포함 여부를 유심히 살펴보기 바랍니다. 


< '2019년 12월 1일' 이전(before) 모든 데이터 잘라내기 예 >

('2019년 11월 30일'까지의 모든 데이터 삭제하며, '2019-12-01'일 데이터는 남아 있음)


# truncate before

series_ts.truncate(before='2019-12-01')

[Out]:
2019-12-01    6
2019-12-02    7
2019-12-03    8
2019-12-04    9
Freq: D, dtype: int64

 



< '2019년 11월 30일' 이후(after) 모든 데이터 잘라내기 예 >

(''2019년 12월 1일' 부터의 모든 데이터 삭제하며, '2019-11-30'일 데이터는 남아 있음)


# truncate after

series_ts.truncate(after='2019-11-30')

[Out]:
2019-11-25    0
2019-11-26    1
2019-11-27    2
2019-11-28    3
2019-11-29    4
2019-11-30    5
Freq: D, dtype: int64

 





  (2) pandas DataFrame에서 시계열 데이터 indexing, slicing, selection, truncation 하는 방법


위의 (1)번에서 소개했던 pandas Series의 시계열 데이터 indexing, slicing, selection, truncation 방법을 동일하게 pandas DataFrame에도 사용할 수 있습니다.  


년-월-일 날짜를 index로 가지는 간단한 pandas DataFrame 예제를 만들어보겠습니다. 



import pandas as pd

from datetime import datetime


# DatetimeIndex

ts_days_idx = pd.date_range('2019-11-25', periods=10)

ts_days_idx

[Out]:
DatetimeIndex(['2019-11-25', '2019-11-26', '2019-11-27', '2019-11-28',
               '2019-11-29', '2019-11-30', '2019-12-01', '2019-12-02',
               '2019-12-03', '2019-12-04'],
              dtype='datetime64[ns]', freq='D')


# DataFrame with DatetimeIndex

df_ts = pd.DataFrame(range(len(ts_days_idx))

                     , columns=['col']

                     , index=ts_days_idx)


df_ts

col
2019-11-250
2019-11-261
2019-11-272
2019-11-283
2019-11-294
2019-11-305
2019-12-016
2019-12-027
2019-12-038
2019-12-049

 




(2-1) 시계열데이터를 index로 가지는 pandas DataFrame에서 특정 날짜-시간 데이터 indexing 하기


위의 (1-1) Series indexing과 거의 유사한데요, DataFrame에서는 df_ts[6], df_ts[datetime(2019, 12, 1)] 의 두가지 방법은 KeyError 가 발생해서 사용할 수 없구요, 아래의 3가지 방법만 indexing에 사용 가능합니다. 


(a) iloc[integer] 메소드를 사용하여 위치(position) 로 indexing 하기

(b), (c) loc['label'] 메소드를 사용하여 이름('label')로 indexing 하기



# (a) indexing with index position integer using iloc[]

df_ts.iloc[6]

[Out]:
col    6
Name: 2019-12-01 00:00:00, dtype: int64


# (b) indexing with index labels ['year-month-day']

df_ts.loc['2019-12-01']

[Out]:
col    6
Name: 2019-12-01 00:00:00, dtype: int64


# (c) indexing with index labels ['month/day/year']

df_ts.loc['12/01/2019']

[Out]:
col    6
Name: 2019-12-01 00:00:00, dtype: int64





(2-2) 시계열데이터를 index로 가지는 pandas DataFrame에서 날짜-시간 데이터 Slicing 하기


아래는 '2019-12-01' 일 이후의 값을 모두 slicing 해오는 4가지 방법입니다. 

(a) 위치(position):위치(position)을 이용하여 날짜를 index로 가지는 Series를 slicing을 하였습니다. 

(b), (c)는 loc['년-월-일']:loc['년-월-일'] 혹은 loc['월/일/년']:loc['월/일/년'] 문자열(string)을 이용하여 slicing을 하였습니다. 

(d) 는 loc[datetime.datetime(year, month, day):datetime.datetime(year, month, day)] 로 slicing을 한 예입니다. 



# (a) slicing DataFrame with position integer

df_ts[6:10]

col
2019-12-016
2019-12-027
2019-12-038
2019-12-049

 


# (b) silcing using date strings 'year-month-day'

df_ts.loc['2019-12-01':'2019-12-10']

col
2019-12-016
2019-12-027
2019-12-038
2019-12-049


# (c) slicing using date strings 'month/day/year'

df_ts.loc['12/01/2019':'12/10/2019']

col
2019-12-016
2019-12-027
2019-12-038
2019-12-049


# (d) slicing using datetime objects

from datetime import datetime

df_ts.loc[datetime(2019, 12, 1):datetime(2019, 12, 10)]

col
2019-12-016
2019-12-027
2019-12-038
2019-12-049






(2-3) 시계열데이터를 index로 가지는 pandas DataFrame 에서 날짜-시간 데이터 Selection 하기


'년', '년-월' 날짜 문자열을 df.loc['year'], df.loc['year-month'] 에 입력하면 해당 년(year), 월(month)의 모든 데이터를 선택할 수 있습니다. 


< '2019년'의 모든 데이터 선택 예 >


# selection of year '2019'

df_ts.loc['2019'] # df_ts['2019']

col
2019-11-250
2019-11-261
2019-11-272
2019-11-283
2019-11-294
2019-11-305
2019-12-016
2019-12-027
2019-12-038
2019-12-049




< '2019년 12월'의 모든 데이터 선택 예 >


# selection of year-month '2019-12'

df_ts.loc['2019-12']

col
2019-12-016
2019-12-027
2019-12-038
2019-12-049

 




(2-4) 시계열 데이터를 index로 가지는 pandas DataFrame에서 날짜-시간 데이터 잘라내기 (Truncate)


truncate() 메소드를 사용하면 before 이전 기간의 데이터를 잘라내거나 after 이후 기간의 데이터를 잘라낼 수 있습니다. 


< '2019-12-01' 일 이전(before) 기간 데이터 잘라내기 예 >

('2019-12-01'일은 삭제되지 않고 남아 있음)


# truncate before

df_ts.truncate(before='2019-12-01') # '2019-12-01' is not removed

col
2019-12-016
2019-12-027
2019-12-038
2019-12-049

 



< '2019-11-30'일 이후(after) 기간 데이터 잘라내기 예 >

('2019-11-30'일은 삭제되지 않고 남아 있음)


# truncate after

df_ts.truncate(after='2019-11-30') # '2019-11-30' is not removed

col
2019-11-250
2019-11-261
2019-11-272
2019-11-283
2019-11-294
2019-11-305

 



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

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



728x90
Posted by R Friend 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 R Friend 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 R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 그룹 별로 변수 간 상관관계 분석 (correlation with columns by groups) 하는 방법을 소개하겠습니다. 


Python pandas는 Pearson Correlation Coefficient를 구할 수 있는 corr(), corrwith() 함수를 제공합니다. 이들 함수를 groupby() 와 apply(lambda func)를 함께 사용함으로써 그룹 별 변수 간 상관계수를 구할 수 있습니다. 



[ 피어슨 상관계수 ( Pearson Correlation Coefficient) ]



먼저 예제로 사용할 'group 1'과 'group 2'의 2개의 그룹을 가진 간단한 DataFrame을 만들어보겠습니다. 




import numpy as np

import pandas as pd

from pandas import DataFrame


# making DataFrame with 4 random variables

np.random.seed(123) # for reproducibility

df= DataFrame(np.random.randn(10, 4), 

                    columns=['a', 'b', 'c', 'd'])

# setting index with 2 group, 'grp1' and 'grp2'

df['group'] = ['grp1', 'grp1', 'grp1', 'grp1', 'grp1', 

                  'grp2', 'grp2', 'grp2', 'grp2', 'grp2']


df = df.set_index('group')


df

abcd
group
grp1-1.0856310.9973450.282978-1.506295
grp1-0.5786001.651437-2.426679-0.428913
grp11.265936-0.866740-0.678886-0.094709
grp11.491390-0.638902-0.443982-0.434351
grp12.2059302.1867861.0040540.386186
grp20.7373691.490732-0.9358341.175829
grp2-1.253881-0.6377520.907105-1.428681
grp2-0.140069-0.861755-0.255619-2.798589
grp2-1.771533-0.6998770.927462-0.173636
grp20.0028460.688223-0.8795360.283627







  (1) 'd' 변수와 나머지 모든 변수 간 그룹 별 상관계수 구하기 : x.corrwith(x['d'])



# correlation with columns: corrwith()

corr_with_d = lambda x: x.corrwith(x['d'])


grouped = df.groupby('group')


grouped.apply(corr_with_d) 

abcd
group
grp10.8468220.0994170.0892051.0
grp20.3074770.832473-0.3904691.0





  (2) 'a'변수와 'd'변수 간 그룹 별 상관계수 구하기 : g['a'].corr[g['d'])



# inter-column correlation: corr()

corr_a_d = lambda g: g['a'].corr(g['d'])


grouped = df.groupby('group')


DataFrame(grouped.apply(corr_a_d))

0
group
grp10.846822
grp20.307477

 



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


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



728x90
Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 groupby() 를 사용할 때 


(1) pd.cut()으로 동일 길이로 나누어서 범주를 만든 후 GroupBy()로 그룹별 통계량 구하기

(2) pd.qcut()으로 동일 개수로 나누어서 범주를 만든 후 GroupBy()로 그룹별 통계량 구하기


를 해보겠습니다. 




먼저, 예제로 사용할 간단한 DataFrame을 만들어보겠습니다. 



import numpy as np

import pandas as pd

from pandas import DataFrame


np.random.seed(123)

df = DataFrame({'col_1': np.random.randint(20, size=20), 

                      'col_2': np.random.randn(20)})


df

col_1col_2
0131.730024
121.232650
22-0.823598
36-0.118201
417-0.576103
5191.695731
610-0.074394
71-1.900637
80-0.777655
9171.313462
10151.804458
119-0.965550
120-1.316480
1314-0.625785
140-0.326946
1515-0.308209
16190.827117
17141.070781
184-3.055577
1901.005932

 




  (1) pd.cut : 동일 길이로 나누어서 범주 만들기(equal-length buckets categorization)


'col_1' 칼럼에 대해서 4개의 동일한 길이로 범주를 만들어보겠습니다. 

카테고리의 구간이 [(-0.019, 4.75] < (4.75, 9.5] < (9.5, 14.25] < (14.25, 19.0]] 로서 4개의 각 구간의 길이가 동일함을 알 수 있습니다.



factor_col_1 = pd.cut(df.col_1, 4)

factor_col_1

0 (9.5, 14.25]

1     (-0.019, 4.75]
2     (-0.019, 4.75]
3        (4.75, 9.5]
4      (14.25, 19.0]
5      (14.25, 19.0]
6       (9.5, 14.25]
7     (-0.019, 4.75]
8     (-0.019, 4.75]
9      (14.25, 19.0]
10     (14.25, 19.0]
11       (4.75, 9.5]
12    (-0.019, 4.75]
13      (9.5, 14.25]
14    (-0.019, 4.75]
15     (14.25, 19.0]
16     (14.25, 19.0]
17      (9.5, 14.25]
18    (-0.019, 4.75]
19    (-0.019, 4.75]
Name: col_1, dtype: category
Categories (4, interval[float64]): [(-0.019, 4.75] < (4.75, 9.5] < (9.5, 14.25] < (14.25, 19.0]]



이제 'factor_col_1'이라는 'col_1' 칼럼에 대한 4개 구간의 범주를 GroupBy() 에 넣어서 각 범주의 그룹별로 agg() 함수로 개수(count), 평균(mean), 표준편차(std), 최소값(min), 최대값(max) 값을 계산해보겠습니다. 



grouped_col_1 = df.col_1.groupby(factor_col_1)

grouped_col_1.agg(['count', 'mean', 'std', 'min', 'max'])

countmeanstdminmax
col_1
(-0.019, 4.75]81.1251.45773804
(4.75, 9.5]27.5002.12132069
(9.5, 14.25]412.7501.8929691014
(14.25, 19.0]617.0001.7888541519



위와 동일한 결과를 아래 처럼 통계집계를 하는 사용자정의함수와 apply() 를 사용해서 구할 수도 있습니다. 


 

def summary_func(group):

    return {'count': group.count()

             'mean': group.mean()

             'std': group.std()

             'min': group.min()

             'max': group.max()}


grouped_col_1.apply(summary_func)

col_1                
(-0.019, 4.75]  count     8.000000
                max       4.000000
                mean      1.125000
                min       0.000000
                std       1.457738
(4.75, 9.5]     count     2.000000
                max       9.000000
                mean      7.500000
                min       6.000000
                std       2.121320
(9.5, 14.25]    count     4.000000
                max      14.000000
                mean     12.750000
                min      10.000000
                std       1.892969
(14.25, 19.0]   count     6.000000
                max      19.000000
                mean     17.000000
                min      15.000000
                std       1.788854
Name: col_1, dtype: float64




위의 결과를 좀더 보기에 좋도록 unstack()를 사용해서 길게(long) 제시된 결과를 옆으로 넓게(wide) 표형식으로 만들어보겠습니다. 



grouped_col_1.apply(summary_func).unstack()

countmaxmeanminstd
col_1
(-0.019, 4.75]8.04.01.1250.01.457738
(4.75, 9.5]2.09.07.5006.02.121320
(9.5, 14.25]4.014.012.75010.01.892969
(14.25, 19.0]6.019.017.00015.01.788854

 



위의 결과의 'count' 개수 부분을 보면 각 범주 구간  [(-0.019, 4.75] < (4.75, 9.5] < (9.5, 14.25] < (14.25, 19.0]] 그룹 별로 개수가 8개, 2개, 4개, 6개로서 각각 다릅니다. 이는 랜덤 숫자에 대해서 구간별 길이를 동일하게 했기 때문에 구간 그룹별 개수가 다르게 된 것입니다.


그러면, 다음으로 구간별 '동일한 개수(equal-size)'로 범주 바구니(bucket categorization)를 만들어보겠습니다.




  (2) pd.qcut() : 동일 개수로 나누어서 범주 만들기 (equal-size buckets categorization)


pd.qcut() 함수를 사용하여 'col_2'에 대해서 각 범주 바구니별로 동일하게 4개의 개수를 가지도록 범주를 만들어보겠습니다. 이때 labels=False 로 설정하여 label이 0, 1, 2, 3 이런 식으로 0부터 순차적으로 1씩 증가하게 하였습니다. 



bucket_qcut_col_2 = pd.qcut(df.col_2, 4, labels=False)

bucket_qcut_col_2

0     3
1     3
2     0
3     2
4     1
5     3
6     2
7     0
8     1
9     3
10    3
11    0
12    0
13    1
14    1
15    1
16    2
17    2
18    0
19    2
Name: col_2, dtype: int64




아래처럼 labels=np.arange(4, 0, -1)로 직접 지정을 해주면 label이 4, 3, 2, 1 이런식으로 4부터 1씩 줄어드는 순서로 할당이 됩니다. 위의 label 과 정 반대로 할당이 되었습니다. 



bucket_qcut_label_col_2 = pd.qcut(df.col_2, 4, labels=np.arange(4, 0, -1))

bucket_qcut_label_col_2

0     1
1     1
2     4
3     2
4     3
5     1
6     2
7     4
8     3
9     1
10    1
11    4
12    4
13    3
14    3
15    3
16    2
17    2
18    4
19    2
Name: col_2, dtype: category
Categories (4, int64): [4 < 3 < 2 < 1]




그럼 [4 < 3 < 2 < 1] 순서로 동일 개수로 나눈 4개의 그룹별 통계량을 계산해보겠습니다. 



grouped = df.col_2.groupby(bucket_qcut_label_col_2)

grouped.apply(summary_func).unstack()

countmaxmeanminstd
col_2
45.0-0.823598-1.612369-3.0555770.907474
35.0-0.308209-0.522940-0.7776550.201746
25.01.0707810.542247-0.1182010.589903
15.01.8044581.5552651.2326500.262163



'count' 개수가 4개의 각 그룹별로 모두 '5'로서 동일한 것을 알 수 있습니다. 


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


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



728x90
Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python pandas의 GroupBy operation을 이용해서 그룹 별 가중평균(Group weighted average)을 구하는 방법을 소개하겠습니다. 


앞서 GroupBy 연산자의 원리에서 소개드렸던 것처럼, Split => Apply => Combine 의 절차를 거치면서 각 그룹별 GroupBy 연산을 실행하게 됩니다. 


[GroupBy 를 이용한 그룹별 가중 평균 구하기 절차]




예제로 사용할 'a'와 'b'의 두 개 그룹별로 'value 값'과 'weight 가중치'를 가지고 있는 간단한 데이터 프레임을 만들어보겠습니다. 



import pandas as pd

import numpy as np


df = pd.DataFrame({'grp_col' : ['a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b'], 

                   'val' : np.arange(10)+1,                   

                   'weight' : [0.0, 0.1, 0.2, 0.3, 0.4, 0.0, 0.1, 0.2, 0.3, 0.4]})


df

grp_colvalweight
0a10.0
1a20.1
2a30.2
3a40.3
4a50.4
5b60.0
6b70.1
7b80.2
8b90.3
9b100.4



  (1) GroupBy 를 활용하여 그룹 별 가중 평균 구하기


이제 (1) GroupBy 객체를 만들고, (2) 가중평균을 구하는 lambda 함수를 정의한 후에, (3) grouped.apply(function) 로 그룹별 가중평균을 구해보겠습니다. 


 

# group weighted average by category

grouped = df.groupby('grp_col')

weighted_avg_func = lambda g:np.average(g['val'], weights=g['weight'])

grouped.apply(weighted_avg_func)

grp_col
a    4.0
b    9.0
dtype: float64




 (2) 수작업으로 그룹 별 가중 평균 구하기 (Split -> Apply -> Combine) 


위에서 처럼 GroupBy 를 사용하지 않는 다면 아래에 소개한 것처럼 각 그룹별로 하나씩 Split -> Apply 하고 마지막에 Combine 을 해주는 단순 반복작업을 그룹의 개수만큼 해주어야 합니다. 


그룹의 개수가 적으면 할만 한데요, 그룹의 개수가 많아지면 수고롭기도 하고, 자칫 실수도 유발할 수 있으니 위의 (1)번 GroupBy 연산자를 사용하는 방법이 좀더 추천할만 하다고 하겠습니다. 비교를 위해서 소개합니다. 


  • (a) Split


# split

df_a = df[df['grp_col']=='a']

df_b = df[df['grp_col']=='b']

 


 df_a

grp_colvalweight
0a10.0
1a20.1
2a30.2
3a40.3
4a50.4

 df_b

grp_colvalweight
5b60.0
6b70.1
7b80.2
8b90.3
9b100.4



  • (b) Apply


# apply

weighted_avg_a = sum((df_a['val']*df_a['weight']))/sum(df_a['weight'])

weighted_avg_b = sum((df_b['val']*df_b['weight']))/sum(df_b['weight'])



 weighted_avg_a

4.0

 weighted_avg_b

9.0



  • (c) Combine


# combine

weighted_avg_ab = pd.DataFrame({'grp_col': ['a', 'b'], 

                               'weighted_average': [weighted_avg_a, weighted_avg_b]})


weighted_avg_ab

grp_colweighted_average
0a4.0
1b

9.0




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



728x90
Posted by R Friend Rfriend

댓글을 달아 주세요