Pandas groupby 함수는 dataframe 자료구조에만 사용가능한 함수가 아닌 series 자료구조에도 사용가능합니다. 일반적으로 dataframe(df).groupby(칼럼 이름) 형식으로 사용하지만 1차원 numpy array, 리스트, 시리즈 같은 array-like 자료구조들도 groupby 함수 인자로 들어갈 수 있다는 것이죠. 이번에는 수천 개의 뉴스 자료들을 모은 News Aggregator 데이터를 이용해보도록 하겠습니다. Seperator를 탭으로 지정해주고 칼럼, dtype, date 칼럼 등을 지정해줍니다.
import datetime as dt
import pandas as pd
def parse_millisecond_timestamp(ts: int) -> dt.datetime:
"""Convert ms since Unix epoch to UTC datetime instance."""
return dt.datetime.fromtimestamp(ts / 1000, tz=dt.timezone.utc)
df = pd.read_csv(
"groupby-data/news.csv",
sep="\t",
header=None,
index_col=0,
names=["title", "url", "outlet", "category", "cluster", "host", "tstamp"],
parse_dates=["tstamp"],
date_parser=parse_millisecond_timestamp,
dtype={
"outlet": "category",
"category": "category",
"cluster": "category",
"host": "category",
},
)
이 상황에서 우리는 어떤 출판사 (outlet)가 "title"에 "Fed" (Federal Reserve)를 얼마만큼 언급하는지를 세고 싶습니다. 먼저 전체 데이터프레임 df에 대해 "outlet" 칼럼 기준으로 groupby를 통해 grouping 하고 "title" 칼럼에 대해 "Fed"가 포함되어있는지 세는 방법이 있겠죠. (Pandas series에 대해 .str.contains("Fed")를 수행하면 "Fed" 포함 여부에 따른 True/False를 리턴합니다.)
>>> df.groupby("outlet", sort=False)["title"].apply(
... lambda ser: ser.str.contains("Fed").sum()
... ).nlargest(10)
outlet
Reuters 161
NASDAQ 103
Businessweek 93
Investing.com 66
Wall Street Journal \(blog\) 61
MarketWatch 56
Moneynews 55
Bloomberg 53
GlobalPost 51
Economic Times 44
Name: title, dtype: int64
위에서 우리는 pandas의 apply 메소드를 사용했습니다. .apply() 메소드는 lambda 함수를 받아 주어진 축에 대해 그 함수를 수행하는 역할을 하죠. .iterrows() 메소드처럼 명시적으로 for 문을 사용하지 않고 readable 하며 속도도 매우 빠른 편입니다. 하지만 위의 경우에서는 각 group에 대해 .apply() 메소드가 내부적으로 루프를 돌게 되어 있고 .apply() 메소드의 인자로 들어가는 lambda 함수는 CPython 상에서 최적화가 안되는 요소기 때문에 완벽한 최적화는 기대할 수 없습니다. 그렇다면 .groupby().apply() 패턴 대신에 어떤 방법을 사용해야 더 빠른 속도로 작업을 수행할 수 있을까요? 명시적인 루프 대신 array expression을 사용하는 vectorization을 이용하면 됩니다.
우리 목적을 다시 한 번 상기해보면 어떤 출판사 (outlet)가 "title"에 "Fed" (Federal Reserve)를 얼마만큼 언급하는지를 세고 싶은 것이고 .groupby() 메소드는 시리즈에 대해서도 사용가능하므로 "title" 칼럼에 대해서 .str.contains("Fed")를 적용한 시리즈를 먼저 구성할 수 있습니다.
>>> mentions_fed = df["title"].str.contains("Fed")
>>> type(mentions_fed)
<class 'pandas.core.series.Series'>
이후에 .groupby() 함수의 인자로 df["outlet"]을 전달하면 시리즈가 "outlet" 종류에 따라 grouping 되게 됩니다. 이때 series1.groupby(series2) 의 두 개의 시리즈는 같은 dataframe의 칼럼일 필요는 없고 같은 크기를 가지면 됩니다.
>>> mentions_fed.shape
(422419,)
>>> df["outlet"].shape
(422419,)
최종적으로 .apply() 메소드를 사용한 것과 시리즈에 대해 .groupby() 메소드의 속도를 비교해 보겠습니다. 컴퓨터에 따라 다르겠지만 version 1의 경우 4초 가량 소요되는 반면 version 2는 0.5초 미만으로 매우 빠르게 수행됩니다. 따라서 데이터가 매우 많을 수록 .apply() 메소드를 사용하기 전에 vectorized로 최적화된 방법이 있을지 먼저 고민해야겠죠.
# Version 1: using `.apply()`
df.groupby("outlet", sort=False)["title"].apply(
lambda ser: ser.str.contains("Fed").sum()
).nlargest(10)
# Version 2: using vectorization
mentions_fed.groupby(
df["outlet"], sort=False
).sum().nlargest(10).astype(np.uintc)
'Computer > Pandas' 카테고리의 다른 글
Pandas Series, DataFrame 이어 붙이기 (0) | 2022.10.13 |
---|---|
여러 데이터 행에 함수 적용하기 (0) | 2022.10.13 |
Pandas - SettingWithCopyWarning (0) | 2021.08.11 |
Pandas groupby (2) (0) | 2021.08.07 |
Pandas groupby (1) (0) | 2021.08.07 |