일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 마케팅 보다는 취준 강연 같다(?)
- 데이터 정합성
- WITH ROLLUP
- 데이터 증식
- 3기가 마지막이라니..!
- 캐글 신용카드 사기 검출
- 부트 스트래핑
- lightgbm
- WITH CUBE
- 로그 변환
- Growth hacking
- 리프 중심 트리 분할
- DENSE_RANK()
- sql
- 그로스 마케팅
- 인프런
- 데이터 핸들링
- 컨브넷
- tableau
- 캐글 산탄데르 고객 만족 예측
- 스태킹 앙상블
- ARIMA
- python
- splitlines
- XGBoost
- 그로스 해킹
- 분석 패널
- ImageDateGenerator
- pmdarima
- 그룹 연산
- Today
- Total
LITTLE BY LITTLE
[ch_04] Hands on Data Analysis With Pandas - Querying, Merging, rolling(), expanding(), ewm, pipe(), transform(), crosstab 본문
[ch_04] Hands on Data Analysis With Pandas - Querying, Merging, rolling(), expanding(), ewm, pipe(), transform(), crosstab
위나 2022. 10. 14. 20:22ch_04
0. weather_data_collection
1. querying and merging
- query()
- merge dataframes 머지 하기 전에
- unique values 개수 확인
- shape[0]으로 열의 개수 동일한지 확인 - 함수 정의 get_row_count
- parameters
- left_on, right_on <=> on( 머지 대상 컬럼이 동일할 경우 중복된 데이터가 없도록 해줌)
- how= 'right', 'left', 'outer' ...
- left_index, right_index => 합치는 대상 컬럼의 '인덱스'가 동일할 경우, 둘다 True로 specify 해줘야함
- 위처럼 동일한 인덱스를 합쳤을 경우, suffixes에 리스트로 지정해주기
2. dataframe_operations
- assign()으로 계산식을 입력하여 전체에 적용, query로 해당 연산 결과가 특정 조건을 만족하는 값만 출력 가능
- 변화가 가장 많은 날 출력 : pct_change()로 계산된 변화율에 rank(ascending=False)를 적용하여 내림차순 순위를 구하고 정렬하여 nsmallest 출력
- .all(), .any()로 특정 조건 만족 여부 확인
- binning()
- 범위를 알고자할 때 사용
- cut() : 같은 범위(range of the same size) , qcut() : 같은 데이터 수(quartiles)
- apply()
- 모든 컬럼에 연산 적용, 계산 식 lambda로 apply()와 같이 잘 쓰임
- len()과 같은 함수는 apply()를 바로 적용할 수 없다. np.vectorize()한 후 apply() <=> applymap() 사용
- windowing()
- rolling() = for문처럼 쓰일 수 있음, 전체가 아닌 x축이 이동하면서 y값의 연산을 수행하는 기능
- window사이즈 정해야함 (ex. 3D => 3 DAYS)
- 여러가지 연산 => df.rolling.agg()
- variable Offset Window Indexer을 윈도우 사이즈로 입력하여 non-fixed time도 rolling 연산할 수 있다.
- expanding()
- 기간의 최솟값 정할 수 있는게 특징, 입력시 그 값만큼 개수가 충족될 경우에만 연산을 수행하고, 그렇지 않을 경우 NaN을 반환함, 새로운 연산 수행 가능, 또한 이미 정의되지 않은 새로운 연산도 수행 가능
- ewm 메소드(지수가중함수) - 최근 데이터가 더 큰 영향을 미치도록 가중치를 주는 함수, 보통 추가로 mean()을 사용해서 지수가중평균으로 사용
- pipes
- rolling이나 새로 정의한 함수를 pass시킬경우, 더 직관적으로 작성할 수 있다는 장점(flexibility↑)
3. aggregations
- df.agg()로 컬럼별 연산을 딕셔너리로 적용
- query()로 특정 데이터의 연산된 컬럼 값 확인
- pd.Grouper(freq='Q', 'M', 'W', '3M', 'Y') 과 같은 빈도로 그룹 연산 수행 가능
- transform()
- df.pivot_table
- 인덱스 여러개, 반복된 값의 인덱스를 다룰 때에는 pivot() 대신 pivot_table() 메소드 사용
- pd.crosstab() = 빈도 테이블 생성
- pd.crosstab( normalize='columns' ) => can get a percentage(%) of total
- crosstab은 원래 빈도 수 세는 계산, 다른 계산 하고싶을 때에는 pd.crosstab(aggfunc=np.mean)
- 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 개수 확인
(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()
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]
2018년에 stock price가 $215보다 높아진 적이 없는지 확인
(fb > 215).any()
(fb > 215).all()
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']
)
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)'
)
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
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]