LITTLE BY LITTLE

[ch_04] Hands on Data Analysis With Pandas - Querying, Merging, rolling(), expanding(), ewm, pipe(), transform(), crosstab 본문

4-2/Hands On Data Aanalysis With Pandas

[ch_04] Hands on Data Analysis With Pandas - Querying, Merging, rolling(), expanding(), ewm, pipe(), transform(), crosstab

위나 2022. 10. 14. 20:22

ch_04

0. weather_data_collection

1. querying and merging

  1. query()
  2. merge dataframes 머지 하기 전에
    1. unique values 개수 확인
    2. shape[0]으로 열의 개수 동일한지 확인 - 함수 정의 get_row_count
    3. parameters
      1. left_on, right_on <=> on( 머지 대상 컬럼이 동일할 경우 중복된 데이터가 없도록 해줌)
      2. how= 'right', 'left', 'outer' ...
      3. left_index, right_index => 합치는 대상 컬럼의 '인덱스'가 동일할 경우, 둘다 True로 specify 해줘야함
      4. 위처럼 동일한 인덱스를 합쳤을 경우, suffixes에 리스트로 지정해주기

2. dataframe_operations

  1. assign()으로 계산식을 입력하여 전체에 적용, query로 해당 연산 결과가 특정 조건을 만족하는 값만 출력 가능
  2. 변화가 가장 많은 날 출력 : pct_change()로 계산된 변화율에 rank(ascending=False)를 적용하여 내림차순 순위를 구하고 정렬하여 nsmallest 출력
  3. .all(), .any()로 특정 조건 만족 여부 확인
  4. binning()
    1. 범위를 알고자할 때 사용
    2. cut() : 같은 범위(range of the same size) , qcut() : 같은 데이터 수(quartiles)
  5. apply()
    1. 모든 컬럼에 연산 적용, 계산 식 lambda로 apply()와 같이 잘 쓰임
    2. len()과 같은 함수는 apply()를 바로 적용할 수 없다. np.vectorize()한 후 apply() <=> applymap() 사용
  6. windowing()
    1. rolling() = for문처럼 쓰일 수 있음, 전체가 아닌 x축이 이동하면서 y값의 연산을 수행하는 기능
    2. window사이즈 정해야함 (ex. 3D => 3 DAYS)
    3. 여러가지 연산 => df.rolling.agg()
    4. variable Offset Window Indexer을 윈도우 사이즈로 입력하여 non-fixed time도 rolling 연산할 수 있다. 
  7. expanding()
    1. 기간의 최솟값 정할 수 있는게 특징, 입력시 그 값만큼 개수가 충족될 경우에만 연산을 수행하고, 그렇지 않을 경우 NaN을 반환함, 새로운 연산 수행 가능, 또한 이미 정의되지 않은 새로운 연산도 수행 가능
    2. ewm 메소드(지수가중함수) - 최근 데이터가 더 큰 영향을 미치도록 가중치를 주는 함수, 보통 추가로 mean()을 사용해서 지수가중평균으로 사용
  8. pipes
    1. rolling이나 새로 정의한 함수를 pass시킬경우, 더 직관적으로 작성할 수 있다는 장점(flexibility↑)

3. aggregations

  1. df.agg()로 컬럼별 연산을 딕셔너리로 적용
  2. query()로 특정 데이터의 연산된 컬럼 값 확인
  3. pd.Grouper(freq='Q', 'M', 'W', '3M', 'Y') 과 같은 빈도로 그룹 연산 수행 가능
  4. transform()
  5. df.pivot_table 
    1. 인덱스 여러개, 반복된 값의 인덱스를 다룰 때에는 pivot() 대신 pivot_table() 메소드 사용
  6. pd.crosstab() = 빈도 테이블 생성
    1. pd.crosstab( normalize='columns' ) => can get a percentage(%) of total
    2. crosstab은 원래 빈도 수 세는 계산, 다른 계산 하고싶을 때에는 pd.crosstab(aggfunc=np.mean)
    3. subtotals도 포함시키기 - margins=True, margins_name으로 이름도 지정

4. time_series

 

 


4-1. querying and merging

Querying Dataframe

: query() 메소드는 filtering할 때 편리하다.  (가독성과 편의성이 장점이지만, loc보다 느리다.)

눈이 왔다고 기록된 'US1NY'가 포함된 이름을 가진 Station만 필터링하고자 할 때

snow_data = weather.query('datatype == "SNOW" and value > 0 and station.str.contains("US1NY")')
snow_data.head()

=> boolean으로 (  == ) & ( == ) .. 이런식으로 입력할 필요 없어서 편리하다.

*그 외 query() 예시

1. == / != / >=
2. in / not in
3. and / or / not
4. 외부 변수(함수) 참조 연산, 외부 변수(함수)명 앞에 @를 붙여 사용한다
5. 인덱스 검색

6. 문자열 부분 검색( str.contains, str.startswith, str.endswith )

### == / != / >=

str_expr = "age == 10"    # 나이가 10 이다 (비교연산자 ==)
df_q = df.query(str_expr) # 조건 부합 데이터 추출

str_expr = "age != 10"     # 나이가 10 아니다 (비교연산자 !=)
df_q = df.query(str_expr)  # 조건 부합 데이터 추출

str_expr = "age >= 22"     # 나이가 22 이상이다 (비교연산자 >=)
df_q = df.query(str_expr)  # 조건 부합 데이터 추출

### in / not in

str_expr = "age in [21, 22]"  # 나이가 21 또는 22이다 (소문자 in 연산자)
df_q = df.query(str_expr)     # 조건 부합 데이터 추출

str_expr = "age not in [21, 22]" # 나이가 21 또는 22 아니다 (소문자 not in 연산자)
df_q = df.query(str_expr)        # 조건 부합 데이터 추출

### and / or / not
str_expr = "(age == 10) and (weight >= 30)" # 나이가 10이고 몸무게가 30kg 이상이다 (소문자 논리연산 and)
df_q = df.query(str_expr)                   # 조건 부합 데이터 추출

### 외부 변수(함수) 참조 연산, 외부 변수(함수)명 앞에 @를 붙여 사용한다
# 외부 변수
num_age    = 10
num_weight = 30
str_expr = "(age == @num_age) and (weight >= @num_weight)"
df_q = df.query(str_expr)       # 조건 부합 데이터 추출

# 외부 함수(내장 함수X)
def my_max(x, y):
    return max(x,y)
str_expr = "age >= @my_max(1,22)"
df_q = df.query(str_expr) # 조건 부합 데이터 추출

### 인덱스 검색
str_expr = "index >= 2"    # 인덱스가 2이상인 데이터
df_q = df.query(str_expr)  # 조건 부합 데이터 추출

## 문자열 부분 검색( str.contains, str.startswith, str.endswith )
data = {"name": ["White tiger", "Tiger black", "Red tiger"], "age": [5, 7, 9]}
df = pd.DataFrame(data)

str_expr = "name.str.contains('tiger')" # 문자열에 tiger 포함
df_q = df.query(str_expr)               # 조건 부합 데이터 추출

str_expr = "name.str.contains('tiger', case=False)" # 문자열에 tiger 포함(대소문자관계없음)
df_q = df.query(str_expr)                           # 조건 부합 데이터 추출

Merging Dataframe

합치기 전에, (1) unique values 개수 확인 

station은 행이 1개
weather는 행이 여러개

(2) 조인시 shape[0]으로 row count를 확인해야한다. data loss 방지 

station_info.shape[0], weather.shape[0]

[Out]

(279, 78780)

* 자주 사용하니 함수로 정의 get_row_count

def get_row_count(*dfs):
    return [df.shape[0] for df in dfs]
get_row_count(station_info, weather)

merge()의 디폴트는 inner join, df1.merge ( df2, left_on = '컬럼1', right_on = '컬럼2')

inner_join = weather.merge(station_info, left_on='station', right_on='id')
inner_join.sample(5, random_state=0)

merge()에서 right_on과 left_on 대신 df1.rename(dict(col1='col2'),axis=1),on='col2) 처럼 on='컬럼'으로 입력해주면, merge에서 left_on과 right_on으로 설정했던 컬럼 station과 id 컬럼에 중복된 데이터가 없도록 합쳐진다.

weather.merge(station_info.rename(dict(id='station'), axis=1), on='station').sample(5, random_state=0)

merge에서 how = 'right', 'left'로 지정해서 right join과 left join 하기 (설정한 반대쪽 데이터가 모두 NaN으로 출력)

여기서의 df1과 df2의 경우, the side가 같아서 차이가 없음

left_join.sort_index(axis=1).sort_values(['date', 'station'], ignore_index=True).equals(
    right_join.sort_index(axis=1).sort_values(['date', 'station'], ignore_index=True)
)

 

left_join = station_info.merge(weather, left_on='id', right_on='station', how='left')
right_join = weather.merge(station_info, left_on='station', right_on='id', how='right')

right_join[right_join.datatype.isna()].head()

merge ( how = 'outer' ) => Full outer join

outer_join = weather.merge(
    station_info[station_info.id.str.contains('US1NY')], 
    l

outer join 결과 확인하기 - query로 station that has US1NY in ID 

pd.concat([
    outer_join.query(f'_merge == "{kind}"').sample(2, random_state=0) 
    for kind in outer_join._merge.unique()
]).sort_index()

both의 경우 NaN이 없는 반면 left_only는 있는 것 확인 가능

dirty_data로 적용

dirty_data = pd.read_csv(
    'data/dirty_data.csv', index_col='date'
).drop_duplicates().drop(columns='SNWD')
dirty_data.head()
# 조인하기 위해서 두 개의 데이터프레임 생성, 불필요한 컬럼 드랍
valid_station = dirty_data.query('station != "?"').drop(columns=['WESF', 'station'])
station_with_wesf = dirty_data.query('station == "?"').drop(columns=['station', 'TOBS', 'TMIN', 'TMAX'])

조인하려는 컬럼의 인덱스가 date로 같아서 left_index와 right_index를 True로 specify해주어야함

valid_station.merge(
    station_with_wesf, how='left', left_index=True, right_index=True
).query('WESF > 0').head()

조인한 두 개의 데이터프레임에 모두 존재하는 컬럼에 대해서, (동일한 컬럼) suffix를 지정해주어 구분해주기

valid_station.merge(
    station_with_wesf, how='left', left_index=True, right_index=True, suffixes=('_x', '_y')
).query('WESF > 0').head()

인덱스를 기준으로 조인했기 때문에, merge보다는 join이 더 적절한 결합 방법

weather.set_index('station', inplace=True)
station_info.set_index('id', inplace=True)

index1.intersection(index2)로 겹치는 index를 확인 => inner join시 출력되는 인덱스 목록

weather.index.intersection(station_info.index)

index1.difference(index2)로 inner join시 잃게되는 부분 확인 가능

weather.index.difference(station_info.index)
# weather에서는 없음
station_info.index.difference(weather.index)
# station은 169개 있음

index1.symmetric_difference(index2)로 양측에서 잃게되는 부분 확인 가능

ny_in_name = station_info[station_info.index.str.contains('US1NY')]

ny_in_name.index.difference(weather.index).shape[0]\
+ weather.index.difference(ny_in_name.index).shape[0]\
== weather.index.symmetric_difference(ny_in_name.index).shape[0]

index1.unique().union(index2)는 full outer join시 출력되는, 즉 1,2의 모든 index를 출력해준다.

weather.index.unique().union(station_info.index)

symmetric difference는 set differences의 union임을 확인

ny_in_name = station_info[station_info.index.str.contains('US1NY')]

ny_in_name.index.difference(weather.index).union(weather.index.difference(ny_in_name.index)).equals(
    weather.index.symmetric_difference(ny_in_name.index)
)

4-2. Dataframe Operations

데이터 보강(enrichment)

 Specify axis to perform the calculation over. (디폴트가 by column)

volume traded에 대해서 sd가 3이상 차이나는 날의 z_score을 구해보자

fb.assign(
    abs_z_score_volume=lambda x: \
        x.volume.sub(x.volume.mean()).div(x.volume.std()).abs()
).query('abs_z_score_volume > 3')

* sub 메소드 : re.sub(찾을 패턴, 대체할 문자, 찾을 문자열)

* div 함수 : 데이터 전체를 특정 column으로 나눌 때 사용

 

rank()pct_change()가장 큰 변화가 있는 날이 언제인지 확인

1. fb.volume.pct_change()로 기간 사이의 변화율을 계산

2. volume_pct_change.abs().rank 로 변화율의 절댓값으로 순위를 매김, 내림차순으로 정렬하여 nsmallest 출력

fb.assign(
    volume_pct_change=fb.volume.pct_change(),
    pct_change_rank=lambda x: \
        x.volume_pct_change.abs().rank(ascending=False)
).nsmallest(5, 'pct_change_rank')

[Out]

=> 1월12일이 가장 변화가 많은 날이고, 그 날은 facebook's ads가 시행된 날

전 날과 같이 확인

fb['2018-01-11':'2018-01-12']

[Out]

volume에 큰 변화

2018년에 stock price가 $215보다 높아진 적이 없는지 확인

(fb > 215).any()

가장 낮은 price인 low가 215보다 높은 적 X

(fb > 215).all()

하지만 215 이하인 날이 다 존재 (at least one)


Binning

volume traded 의 정확한 값보다는 범위를 아는 것이 유용

volume traded #가 같은 날은 하루도 없음을 확인 (value_counts() > 1로)

(fb.volume.value_counts() > 1).sum()
# 0

cut() : equally-sized bins 생성

pd.cut()으로 volume traded를 3개의 구간으로 나누자 (low, medium, high 카테고리로)

volume_binned = pd.cut(
    fb.volume, bins=3, labels = ['low', 'med',' high']
)

cut은 같은 수로 나누지 X, "구간"의 차이 기준으로 나눈 것!!

high에 속한 데이터 확인

fb[volume_binned == 'high'].sort_values('volume', ascending=False)

7월 25일에 disappointing user growht를 기록했다고 한다. 확인해보자 (그 후 stock tanked  in the after hours)

fb['2018-07-25':'2018-07-26']

Most days가 비슷한 volume을 갖고 있고, 소수만 아주 크기 때문에, we have very wide bins. (대부분의 데이터가 low bin에 몰려있다.)

from visual_aids.misc_viz import low_med_high_bins_viz

low_med_high_bins_viz(
    fb, 'volume', ylabel='volume traded',
    title='Daily Volume Traded of Facebook Stock in 2018 (with bins)'
)

=> 시각화를 해보니, cut을이용해 나누었던 것이 적절치 않다는 것을 알 수 있음(high에 거의 없고, low에 대부분. .)

quantiles를 이용하는 qcut으로 나눌시 같은 obs 개수를 갖도록 나눌 수 있다. 4 quartiles로 나눠보자

qcut() : each bin 에 같은 수의 observations

volume_qbinned = pd.qcut(
    fb.volume, q=4, labels= ['q1','q2','q3','q4']
)
volume_qbinned.value_counts()

[Out]

q1    63
q2    63
q4    63
q3    62
Name: volume, dtype: int64

=> ranges of the same size가 아닌 quartiles로 binning되었다.

from visual_aids.misc_viz import quartile_bins_viz

quartile_bins_viz(
    fb, 'volume', ylabel='volume traded', 
    title='Daily Volume Traded of Facebook Stock in 2018 (with quartile bins)'
)

cut과 달리 qcut으로 개수를 같도록 나누어 bin 별로 균일하게 나누어짐


Applying

apply() : 모든 컬럼에 같은 코드 적용

Weather 데이터에서 station이 central park인 데이터만 추출하여 Central park weather df 생성

central_park_weather = weather\
    .query('station == "GHCND:USW00094728"')\
    .pivot(index='date', columns='datatype', values='value')

apply로 (10월)모든 컬럼의 (TMIN, TMAX, PRCP) Z-score 계산하기 ( x.sub(x.mean()).div(x.std()) <= z-score 계산 식)

oct_weather_z_scores = central_park_weather\
    .loc['2018-10', ['TMIN', 'TMAX', 'PRCP']]\
    .apply(lambda x: x.sub(x.mean()).div(x.std()))
oct_weather_z_scores.describe().T

=> PRCP(precipitation) max가 3.9이므로, 3이상인 날을 출력해보자

oct_weather_z_scores.query('PRCP > 3').PRCP

[Out]

date
2018-10-27    3.936167
Name: PRCP, dtype: float64

 

np.vectorize()로 벡터화하고 apply() 적용하거나,  applymap() 으로 directly pass it the non-vectorized function

facebook 데이터에서 모든 값의 자리수를 세고자 한다면, len() is not vectorized, so 이런 경우에 사용해야 함

fb.apply(
    lambda x: np.vectorize(lambda y: len(str(np.ceil(y))))(x)
).astype('int64').equals(
    fb.applymap(lambda x: len(str(np.ceil(x))))
)

iteritmes()를 사용하면 a simple operation of addition to each element in a series grows linearly in time complexity, but stays near 0 when using vectorized operations. 하지만 vectorized operation을 사용하면 stays near 0.

=> iteritmes()와 관련 메소드는 vectorized solution이 없을 때에만 사용되어야한다.


Windowing

rolling() 

: for문 대신 간편하게 계산 가능, 전체가 아닌, x축의 창이 이동하면서 y값의 평균,최소,최댓값 등을 구하는 경우 사용

specify the window size (ex.3 days) & follow it with a call to an aggregation function ( ex.sum)

* 정수 인덱스를 윈도우 사이즈로 설정할 경우 정수만 괄호 안에 써주면 된다

central_park_weather.loc['2018-10'].assign(
    rolling_PRCP=lambda x: x.PRCP.rolling('3D').sum()
)[['PRCP', 'rolling_PRCP']].head(7).T

윈도우 사이즈를 3으로 설정하여, 3일 단위로 이동하면서 1달 평균을 구함

2018-10 포함 전체 데이터에 rolling(3) mean 적용

central_park_weather.loc['2018-10'].rolling('3D').mean().head(7).iloc[:,:6]

df.rolling.agg()로 연산도 여러가지 적용할 수 있다. 딕셔너리로 컬럼별로 다른 연산 적용

central_park_weather['2018-10-01':'2018-10-07'].rolling('3D').agg(
    {'TMAX': 'max', 'TMIN': 'min', 'AWND': 'mean', 'PRCP': 'sum'}
).join( # 비교하기 위해서 원본 데이터와 조인
    central_park_weather[['TMAX', 'TMIN', 'AWND', 'PRCP']], 
    lsuffix='_rolling'
).sort_index(axis=1) # sort columns so rolling calcs are next to originals

페이스북 주식 데이터를 reindex하고, rolling 연산을 이용해서 시장이 닫혔을 때의 값도 포함시켜보자

fb_reindexed = fb\
    .reindex(pd.date_range('2018-01-01', '2018-12-31', freq='D'))\
    .assign(
        volume=lambda x: x.volume.fillna(0),
        close=lambda x: x.close.fillna(method='ffill'),
        open=lambda x: x.open.combine_first(x.close),
        high=lambda x: x.high.combine_first(x.close),
        low=lambda x: x.low.combine_first(x.close)
    )
fb_reindexed.assign(day=lambda x: x.index.day_name()).head(5)

Variable OffSet Window Indexer Class를 사용할 경우, rolling 연산을 non-fixed time offsets(ex. business days)을 대상으로도 수행할 수 있다. (??)

three business day rolling calculation을 facbook stock data로 수행해보자

from pandas.api.indexers import VariableOffsetWindowIndexer

indexer = VariableOffsetWindowIndexer(
    index=fb_reindexed.index, offset=pd.offsets.BDay(3)
)
fb_reindexed.assign(window_start_day=0).rolling(indexer).agg({
    'window_start_day': lambda x: x.index.min().timestamp(),
    'open': 'mean', 'high': 'max', 'low': 'min',
    'close': 'mean', 'volume': 'sum'
})

expanding()

: cumsum()과 비슷한 기능으로, 기간의 minimum을 설정해줄 수 있고, 이미 정의되지 않은 연산도 수행할 수 있다는 차이점이 있다.

cumulative mean 이라는 연산은 존재하지 않지만, expanding()으로 계산할 수 있다. month-to-date average precipitation을 계산해보자 ( x.PRCP.cumsum()으로 합계를 계산한뒤, x.PRCP.expanding().mean() 계산)

central_park_weather.loc['2018-06'].assign(
    TOTAL_PRCP=lambda x: x.PRCP.cumsum(),
    AVG_PRCP=lambda x: x.PRCP.expanding().mean()
).head(10)[['PRCP', 'TOTAL_PRCP', 'AVG_PRCP']].T

ewm (Exponentially Weighted Moving Windows) 메소드

최고기온(TAMX)의 원본, ewm을 적용한 평균, Rolling mean을 비교해보자

central_park_weather.assign(
    AVG=lambda x: x.TMAX.rolling('30D').mean(),
    EWMA=lambda x: x.TMAX.ewm(span=30).mean()
).loc['2018-09-29':'2018-10-08', ['TMAX', 'EWMA', 'AVG']].T


Pipes

코드를 단순화하고 보기 쉽게 만듦

can pass any function

def get_info(df):
    return '%d rows, %d columns and max closing Z-score was %d' % (*df.shape, df.close.max())

# pipe 적용 X
get_info(fb.loc['2018-Q1'].apply(lambda x: (x - x.mean())/x.std()))

# pipe 적용 O
fb.loc['2018-Q1'].apply(lambda x: (x - x.mean())/x.std()).pipe(get_info)

pipe()으로 rolling을 pass하는 것과 그냥 directly 하는 방법은 동일하지만, flexibility면에서 다름

fb.pipe(pd.DataFrame.rolling, '20D').mean().equals(fb.rolling('20D').mean())

* 필요한 모든 window calculation을 수행하는 함수를 만들 때 pipe을 사용할 수 있다. 


4-3. Aggregating Data With Pandas And Numpy

Summarizing Dataframes (agg 이용)

fb.agg({
    'open': np.mean, 
    'high': np.max, 
    'low': np.min, 
    'close': np.mean, 
    'volume': np.sum
})

[Out]

open            171.45
high            218.62
low             123.02
close           171.51
volume   6949682394.00
dtype: float64

쿼리로 station이 central park인 곳의 전체 눈과 강수량 값 확인하기

weather.query('station == "GHCND:USW00094728"')\
    .pivot(columns='datatype', values='value')[['SNOW', 'PRCP']]\
    .sum()

sum을 agg에 넣어도 같은 결과 출력

weather.query('station == "GHCND:USW00094728"')\
    .pivot(columns='datatype', values='value')[['SNOW', 'PRCP']]\
    .agg('sum')

연산 여러개 적용시 series가 아닌 df로 출력됨

fb.agg({
    'open': 'mean',
    'high': ['min', 'max'],
    'low': ['min', 'max'],
    'close': 'mean'
})

groupby()로 전체가 아닌 특정 컬럼만 연산 적용, groupby()한 후에 agg() 가능

fb.groupby('trading_volume').mean()
fb.groupby('trading_volume')['close'].agg(['min', 'max', 'mean'])
fb_agg = fb.groupby('trading_volume').agg({
    'open': 'mean',
    'high': ['min', 'max'],
    'low': ['min', 'max'],
    'close': 'mean'
})
fb_agg

컬럼 입력시 튜플로 출력됨

fb_agg.columns

튜플 join해서 잇기

fb_agg.columns = ['_'.join(col_agg) for col_agg in fb_agg.columns]
fb_agg.head()

groupby()로 index 합치기 

weather.loc['2018-10'].query('datatype == "PRCP"')\
    .groupby(level=0).mean().head().squeeze()

pd.Grouper(freq='Q') : Quarter 분기별로 groupby()대상의 연산 값 출력

weather.query('datatype == "PRCP"').groupby(
    ['station_name', pd.Grouper(freq='Q')]
).sum().unstack().sample(5, random_state=1)

[Out]

1. filter()로 연산 대상 그룹을 제외시키고, => 2. query로 NY US를 제외시키기

* filter는 하나만 필터할 때에는 단순하지만, and / or 조건식을 사용할 경우 복잡하기 때문에 그런 경우 쿼리를 사용하는 것

weather.groupby('station_name').filter( # station names with "NY US" in them
    lambda x: x.name.endswith('NY US')
).query('datatype == "SNOW"').groupby('station_name').sum().squeeze() # aggregate and make a series (s

* squeeze차원 축소 (ex. 1개의 인덱스를 가진 시리즈를 squeeze하면 스칼라 값이 된다.)

[Out]

결과 예시

가장 비가 많이 내린 날 출력

1. datatype이 prcp인 데이터만 query로 필터링한다.

2. station의 평균 강수량을 계산한다.

3. groupby(level=0) => level은 index의 depth를 의미하며, 가장 왼쪽에서 0부터 증가한다. 

4. 월별로 합계를 계산 (<-pd.Grouper(freq='M') )

5. nlargest()로 가장 많이 온 5개만 출력

weather.query('datatype == "PRCP"')\
    .groupby(level=0).mean()\
    .groupby(pd.Grouper(freq='M')).sum().value.nlargest()

[Out]

date
2018-11-30   210.59
2018-09-30   193.09
2018-08-31   192.45
2018-07-31   160.98
2018-02-28   158.11
Name: value, dtype: float64

월별 sum이 아닌 percentage를 계산해보자 (sum을 mean으로 transform 시키는 방법)

* transform() 함수

: 기존의 특징으로부터 새로운 특징을 추출하는 함수라는 점에서 apply()와 다름,,

ex. 한 구매자의 여러가지 구매내역 => 각 구매자들의 평균 구매내역을 알고자할 경우

: groupy() 함수 => merge() 함수 => transform() 함수 사용

weather.query('datatype == "PRCP"')\
    .rename(dict(value='prcp'), axis=1)\
    .groupby(level=0).mean()\
    .groupby(pd.Grouper(freq='M'))\
    .transform(np.sum)['2018-01-28':'2018-02-03']

[Out]

1월 28일과 29일에 어떻게 같은 값이 나올 수 있었는지 확인해보자

일자별로 월별 강수량의 비율 확인, 가장 큰 값 출력하기

1. 강수량인 것만 query로 필터

2. groupby해서 mean계산

3. assign으로 grouper 이용해서 월별로 하계 계산 ,

4. transform(np.sum) 

5. 3,4번 결과 div로 나눠서 일자별 월별 강수량 비율 출력

weather\
    .query('datatype == "PRCP"')\
    .rename(dict(value='prcp'), axis=1)\
    .groupby(level=0).mean()\
    .assign(
        total_prcp_in_month=lambda x: \
            x.groupby(pd.Grouper(freq='M')).transform(np.sum),
        pct_monthly_prcp=lambda x: \
            x.prcp.div(x.total_prcp_in_month)
    )\
    .nlargest(5, 'pct_monthly_prcp')

[Out]

transform()은 데이터프레임에도 쓰일 수 있다. (데이터 정규화시 사용)

fb[['open', 'high', 'low', 'close']]\
    .transform(lambda x: (x - x.mean()).div(x.std()))\
    .head()

Pivot tables and Crosstabs

피벗 테이블을 이용해서 default로 mean을 구해보자

fb.pivot_table(columns='trading_volume')

[Out]

trading volume 컬럼을 인덱스로 설정하면, 전치된 데이터프레임을 얻을 수 있음

fb.pivot_table(index='trading_volume')

[Out]

pivot() 함수로는 multi-level 인덱스나 반복된 값의 인덱스를 다룰 수 없다.

=> pivot_table() 메소드가 이러한 문제를 해결해준다.

weather.reset_index().pivot_table(
    index=['date', 'station', 'station_name'], 
    columns='datatype', 
    values='value',
    aggfunc='median'
).reset_index().tail()

[Out]

pd.crosstab()으로 빈도 테이블을 생성하여 low, med, high별로 how many traiding days 인지 월별로 확인 가능

pd.crosstab(
    index=fb.trading_volume,
    columns=fb.index.month,
    colnames=['month'] # name the columns index
)

[Out]

pd.crosstab()normalize 파라미터로 전체 행과 열을 정규화시킬 수 있다. 전체의 %를 알 수 있음

pd.crosstab(
    index=fb.trading_volume,
    columns=fb.index.month,
    colnames=['month'],
    normalize='columns'
)

[Out]

빈도수를 세는 계산말고 다른 계산을 하고싶은 경우, pass the column to run the calculation on to values and the function to use to aggfunc (ex. pd.crosstab( aggfunc=np.mean

pd.crosstab(
    index=fb.trading_volume,
    columns=fb.index.month,
    colnames=['month'],
    values=fb.close,
    aggfunc=np.mean
)

행과 열의 subtotals를 margins 파라미터로도 얻을 수 있다. station별로 월별 강우량을 구하고, 데이터프레임에 포함시키자

snow_data = weather.query('datatype == "SNOW"')
pd.crosstab(
    index=snow_data.station_name,
    columns=snow_data.index.month,
    colnames=['month'],
    values=snow_data.value,
    aggfunc=lambda x: (x > 0).sum(),
    margins=True, # show row and column subtotals
    margins_name='total observations of snow' # name the subtotals
)

[Out]

 

Comments