Notebook 데이터 집합을 분류하고 각 그룹별로 집계나 변형 같은 함수를 적용하는 것은 데이터 분석 과정에서 중요. 데이터를 불러오고 취합해서 하나의 데이터 집합을 준비하고나면 그룹 통계를 구하거나 피벗 테이블을 구해서 보고서를 만들거나 시각화하게 됨. pandas는 데이터 집합을 자연스럽게 나누고 요약할 수 있는 groupby라는 유연한 방법을 제공. 관계형 데이터베이스와 SQL의 쿼리문은 그룹 연산에 제약이 있으나, 파이썬의 pandas 객체나 NumPy 배열을 받는 함수의 조합을 통해 복잡한 그룹 연산도 가능. 이 장에서 다루는 내용 1. 하나 이상의 키(함수, 배열, DataFrame의 칼럼 이름)를 이용해 pandas 객체를 여러 조각으로 나누는 법 2. 합계, 평균, 표준편차, 사용자 정의 함수 같은 그룹 요약통계를 계산 3. DataFrame의 각 칼럼에 다양한 함수를 적용 4. 정규화, 선형 회귀, 등급, 부분집합 선택 같은 집단 내 변형 적용 5. 피벗 테이블과 교차일람표 구하는 법 6. 변위치 분석과 다른 데이터 파생 집단 분석 * 시계열 데이터 집계 같은 특수한 groupby 사용 방법인 리샘플링은 10장에서 다룰 예정
그룹 연산: 분리-적용-결합 1. 분리: Series. DataFrame과 같은 pandas 객체나 다른 객체에 들어 있는 데이터를 하나 이상의 색인을 기준으로 분리. (분리 기준: row(axis=0)나 column(axis=1)) 2. 적용: 함수를 각 그룹에 적용시켜 새로운 값을 얻어냄. 3. 결합: 함수를 적용한 결과를 하나의 객체로 결합. 결과를 담는 객체는 보통 데이터에 어떤 '연산'을 했는지에 따라 결정.그룹 연산의 예 (그림 9-1) 그룹 색인의 형태 1. 그룹으로 묶을 축과 같은 길이의 리스트나 배열 2. DataFrame의 칼럼 이름을 지칭하는 값 3. 그룹으로 묶을 값과 그룹 이름에 대응하는 사전이나 Series 객체 4. 축 색인 또는 색인 내의 개별 이름에 대해 실행되는 함수 2-4는 모든 객체를 나눌때 사용할 배열을 생성하기 위한 방법
데이터를 key1으로 묶고 각 그룹에서 data1의 평균을 구하는 방법: data1에 대해 groupby 메서드를 호출하고 key1 칼럼을 넘김
grouped 변수는 Groupby 객체다. df['key1']으로 참조되는 중간 값에 대한 것 외에는 아무것도 계산되지 않은 객체. 각 그룹에 대한 연산을 적용할 수 있게 해줌.
데이터(Series 객체)가 그룹의 색인에 따라 수집되고 key1 칼럼에 있는 유일한 값으로 색인되는 새로운 Series 객체가 생성됨. 객체의 색인은 'key1' 여러개의 배열을 리스트로 넘긴 경우는 다음과 같음
이 경우는 데이터를 2개의 색인으로 묶었고, 그 결과 계층적 색인을 가지는 Series를 얻을 수 있었음
다음 예제는 그룹의 색인이 모두 Series 객체인데, 길이만 같다면 어떤 배열이라도 상관 없음
한 그룹으로 묶을 정보는 같은 DataFrame 안에서 주로 찾게 되는데, 이 경우 칼럼 이름을 넘겨서 그룹의 색인으로 사용할 수 있음
위에서 df.groupby('key1').mean() 코드에서 key2 칼럼이 결과에서 빠졌는데 숫자 데이터가 아니기 때문에 '성가신 칼럼'으로 결과에서 제외시키고 원하는 부분만 따로 걸러내는 것이 가능. size 메서드: 그룹의 크기를 담고 있는 Series를 반환. 현재는 그룹의 색인에서 누락된 값은 결과에서 제외되나 추후 NA 그룹을 결과에 포함시키도록 하는 옵션이 추가될 예정.
GroupBy 객체는 이터레이션을 지원함. 그룹 이름과 해당 데이터 묶음을 튜플로 반환.
groupby 메서드는 axis=0(default),1에 대해 그룹을 만들 수 있음. 다음 예에서 df DataFrame의 dtype에 따라 그룹을 묶을 수 있음
그룹 정보는 배열이 아닌 형태로 존재하기도 함.
각 칼럼을 나타낼 그룹 목록별로 칼럼 값을 더하자.
이 사전에서 groupby 메서드로 넘길 배열을 뽑아낼 수도 있지만, 이 사전을 groupby 메서드로 넘겨보자.
Series에 대해서도 같은 기능을 수행할 수 있는데, 고정된 크기의 매핑이라고 보면 됨. Series를 그룹의 색인으로 사용한 경우 pandas 내부에서 각 Series의 색인이 그룹으로 묶는 축과 맞아 떨어지는지 확인해야 함.
사전이나 Series를 사용하는 것과 비해 파이썬 함수를 사용해서 그룹을 매핑하는 것은 좀 더 독창적이며 추상화된 방법. 그룹 색인으로 넘긴 함수는 색인 값 하나마다 한 번씩 호출되며, 반환 값은 그 그룹의 이름으로 사용됨. 즉, 앞의 예제에서 people DataFrame은 사람의 이름을 색인 값으로 사용됨. 이름의 길이별로 그룹을 묶으려면 이름의 길이가 담긴 배열을 생성하는 대신 len 함수를 넘기면 됨.
내부적으로는 모두 배열로 반환되므로 함수와 배열, 사전 또는 Series를 함께 섞어 쓰는 것도 가능.
계층적으로 색인된 데이터 묶음은 축 색인의 단계 중 하나를 사용해서 편리하게 모을 수 있는 기능을 제공. 이를 위해 level 인자를 통해 레벨 번호나 이름을 넘면 됨.
데이터 수집이라고 하면 보통 배열로부터 스칼라 값을 생산해내는 데이터 변환 작업을 떠올리게 됨. 위 예에서는 mean, count, min, sum을 통해 스칼라 값을 구했다. Groupby 객체에 대해 mean()을 수행하게 되면 어떤일이 생길까? [표 9-1]에 있는 것과 같은 많은 일반적인 데이터 수집은 데이터 묶음에 대한 준비된 통계를 계산해내는 최적화된 구현을 가지고 있음. 하지만 직접 고안한 방식과 추가적으로 기존 메서드를 호출하는 방식으로도 가능. Series나 DataFrame의 칼럼에 대한 변위치를 계산하는 quantile 이라는 메서드를 살펴보자.
내부적으로 GroupBy는 Series를 효과적으로 잘게 잘라서 각 조각에 대해 piece.quantile(0.9)를 호출하여 그 결과를 모두 하나의 객체로 합쳐서 반환. 자신만의 데이터 집계 함수를 사용하려면 배열의 egg나 aggregate 메서드에 해당 함수를 넘기면 됨.
그룹별 연산과 변형에 대한 추가 내용은 다은 절에서 다루도록 함. NOTE_사용자 집계 함수는 중간 데이터를 생성하는 과정 중에 함수 호출이나 데이터 정렬 같은 오버헤드가 발생하기 때문에 [표9-1]에 있는 최적화된 함수에 비해 무척 느리게 수행됨. 표 9-1 최적화된 groupby 메서드 count: 그룹 내에 NA가 아닌 값의 수를 반환 sum: NA가 아닌 값의 합 mean: NA가 아닌 값의 평균 median: NA가 아닌 값의 산술 중간 값 std, var: 편향되지 않은 (n-1을 분모로 하는) 표준 편차와 분산 min, max: NA가 아닌 값의 최소 값과 최대 값 prod: NA가 아닌 값의 곱 first, last: NA가 아닌 값 중 첫번째와 마지막 값집계의 고성능 기능을 설명하기 위해 레스토랑 팁에 대한 테이터를 사용하고자 함. 원 출처는 1995년 비즈니스 통계에서 Briant Smith가 발표한 자료이며, R의 reshape2 패키지에서 가져옴. read_csv 함수를 사용해 데이터를 불러온 후에 팁의 비율을 담기 위한 칼럼인 tip_pct를 추가함.
지금까지 Series나 DataFrame의 모든 칼럼을 집계를 위해 mean, std 같은 메서드를 호출하거나 원하는 함수를 aggregate를 통해서 사용. 이제 칼럼에 따라 다른 함수를 사용하거나 여러 개의 함수를 한 번에 적용하고 싶은 간단한 다른 예를 살펴보자. 먼저 tips를 sex와 smoker별로 묶어보자.
[표9-1]과 같은 기술통계에서는 함수 이름을 문자열로 넘기면 됨.
함수의 목록이나 이름을 넘기면 함수 이름을 칼럼 이름으로 하는 DataFrame을 얻게 됨.
GroupBy 객체에서 자동으로 지정되는 칼럼 이름을 그대로 쓰지 않아도 됨. lamda 함수는 이름(__name__ 속성으로 확인 가능)이 '<lamda>'인데, 이를 그대로 쓰면 알아보기 힘들다. 이름과 함수가 담긴 튜플의 리스트를 넘기면 각 튜플에서 첫 번째 원소는 DataFrame에서 칼럼의 이름으로 사용됨 (2개의 튜플을 가지는 리스트가 순서대로 연결).
DataFrame은 칼럼마다 다른 함수를 적용하거나 여러 개의 함수를 모든 칼럼에 적용할 수 있다. tip_pct와 total_bill 칼럼에서 세 가지 통계를 계산하자.
반환된 DataFrame은 계층적인 칼럼을 가지고 있으며, 이는 각 칼럼을 따로 계산한 다음 concat 메서드를 이용해서 keys 인자로 칼럼 이름을 넘겨서 이어붙인 것과 동일.
칼럼 이름과 메서드가 담긴 튜플의 리스트를 넘기는 것도 가능.
칼럼마다 다른 함수를 적용하고 싶다면 agg 메서드에 칼럼 이름에 대응하는 함수가 들어 있는 사전을 넘기면 됨. 이때 단 하나의 칼럼이라도 여러 개의 함수가 적용된다면 DataFrame은 계층적인 칼럼을 가짐.
지금까지는 유일한 그룹키 조합으로 색인(때론 계층적 색인)이 반환됨. groupby 메서드에 as_index=False를 넘겨서 색인되지 않도록 할 수 있음.
색인된 결과에 대해 reset_index 메서드를 호출해 같은 결과를 얻을 수도 있음. TIP groupby 메서드를 이렇게 사용하는 것은 일반적으로 유연하지 않는 경우. 현재 구현에서는 결과가 계층적 상속을 가지게 되는 경우 임의의 크기가 되도록 구현되어 있지 않음.
집계는 1차원 배열을 스칼라 값으로 줄여주는 함수를 적용하는 일반적인 데이터 변형의 한 가지 특수한 그룹 연산. 다양한 그룹 연산을 수행할 수 있는 transform과 apply 메서드를 살펴보자. DataFrame에서 각 색인별로 그룹의 평균 값을 담기 위한 칼럼을 하나 추가하는 한가지 방법은 먼저 데이터를 집계한 후 병합하는 것.
위의 내용은 두 데이터 칼럼을 np.mean 함수를 사용해서 변형하는 작업임. 이제 GroupBy 객체에 대해 transform 메서드를 호출해 보자.
transform 메서드가 각 그룹마다 적용되었고 적절한 위치에 결과 값이 놓임. 각 그룹이 스칼라 값을 제공한다면 그 값은 전파될 것. 각 그룹에서 평균 값을 빼고 싶다면 함수를 하나 만들어 transform 메서드에 넘기면 된다.
demeaned 객체 그룹 평균은 0이 됨. 아래 과정은 apply를 사용해서 처리 가능.
aggregate와 마찬가지로 transfrom은 엄격한 요구사항을 갖는 특수한 목적의 함수. 인자로 넘긴 함수는 반드시 스칼라 값 (np.mean처럼) 또는 같은 크기를 가지는 변형된 배열을 생성해야 함. 가장 일반적인 GroupBy 메서드의 목적은 apply. [그림 9-1]에서 봤듯이 apply 메서드는 객체를 여러 조각으로 나누어 전달된 함수를 각 조각에 일괄적으로 적용한 후 이를 다시 합치게 됨. 앞의 팁 데이터에서 그룹별 상위 5개의 tip_pct 값을 골라보자. 우선 특정 칼럼에서 가장 큰 값을 가지는 행을 선택하는 함수를 작성한다.
smoker 그룹에 대해서 이 함수를 apply하게 되면 다음과 같은 결과를 얻을 수 있음.
이 결과에서 top 함수가 나누어진 DataFrame의 각 부분에 모두 적용이 되었고, pandas.concat을 이용해서 하나로 합쳐진 다음 그룹 이름표가 붙여짐. 결과는 계층적 색인을 가지게 되고 내부 색인은 원본 DataFrame의 색인 값을 가지게 됨. apply 메서드에 넘길 함수가 추가적인 인자를 받는다면 함수 이름 뒤에 붙여서 넘겨주면 됨.
GroupBy 객체에 describe 메서드 호출하면 내부적으로 다음과 같은 단계를 거침. f = lamda x: x.describe(); grouped.apply(f)
그룹 색인 생략하기 : 반환된 객체들은 원본 객체의 각 조각에 대 색인과 그룹 키가 계층적 색인으로 사용됨. 이런 결과는 groupby 메서드에 group_keys=False를 넘겨서 막을 수 있음.
7장에서 pandas의 cut, qcut 메서드를 사용해서 선택한 크기만큼 또는 표본 변위치에 따라 데이터를 나눌 수 있음. 이 함수를 groupby와 조합하면 데이터 묶음에 대해 변위치 분석이나 버킷 분석을 쉽게 수행할 수 있음. 다음 임의의 데이터 묶음을 cut을 이용해서 등간격 구간으로 나누어 보자.
cut 메서드는 Factor 객체를 반환하는데, 이 Factor 객체는 바로 groupby로 넘길 수 있음. 그래서 data2 칼럼에 대한 몇 가지 통계를 다음과 같이 계산할 수 있음.
이는 등간격 버킷이고, 표본 변위치에 기반하여 크기가 같은 버킷을 계산하려면 qcut을 사용해야 함. 여기서는 labels=False를 넘겨서 변위치 숫자를 구함.
누락된 데이터를 정의하는 한 방법은 dropna. 다른 방법인 fillna 메서드를 사용해서 누락된 값을 고정된 값이나 데이터로부터 도출된 어떤 값으로 채울 수 있음.
그룹별로 채워 넣고 싶은 값이 다르면 데이터를 그룹으로 나누고 apply 함수를 사용해서 각 그룹에 대해 fillna를 적용. 다음 예는 미국 주를 동부와 서부로 나눈 데이터.
대용량의 데이터를 몬테카를로 시뮬레이션이나 다른 애플리케이션에서 사용하기 위해 랜덤 표본을 뽑아낸다고 하자. 한 방법은 np.random.permutation(N)에서 처음 K 원소를 선택하는 것. (N: 전체 크기, K: 표본 크기) 트램프 카드 덱을 만들어 보자.
각 무늬(각 카드 이름의 마지막 글자로 나타냄)별로 apply를 사용해 2장의 카드를 무작위로 뽑자.
groupby의 나누고 적용하고 합치는 패러다임에서 그룹 가중 평균 같은 DataFrame에서 칼럼간의 연산이나 두 Series 간의 연산은 일상적인 일. 다음의 그룹 키와 값, 어떤 가중치를 갖는 데이터 묶음을 살펴보자.
복잡한 예제로, 아후!파이낸스에서 가져온 몇몇 주식과 S&P 500 지수(종목 코드 SPX)의 종가 데이터를 살펴보자.
퍼센트 변화율로 일일 수익률을 계산하여 연간 SPX 지수와의 상관관계를 살펴보자.
pandas 객체나 스칼라 값을 반환하기만 하면 groupby를 좀 더 복잡한 그룹의 통계 분석을 위해 사용 가능. 일례로 계량 경제 라이브러리인 statsmodels를 사용해 regress 함수를 작성하고 각 데이터 묶음마다 최소제곱으로 회귀를 수행할 수 있음.
피벗 테이블은 테이터를 하나 이상의 키로 수집해서 어떤 키는 행에, 어떤 키는 열에 나열해서 데이터를 정렬. pandas에서 피벗 테이블은 groupby 기능을 사용해서 계층적 색인을 활용한 재형성 연산을 가능하게 해줌. DataFrame의 pivot_table 메서드는 pandas 모듈의 최상위 함수이며 마진이라고 하는 부분합을 추가 할수 있는 기능을 통해 편리한 인터페이스를 제공.
tip_pct와 size에 대해서만 집계를 하고 날짜별로 그룹을 지어보자. 이를 위해 day 행과 smoker 열을 추가.
이 표는 margins=True를 넘겨서 부분합을 포함하도록 확장 가능. 그러면 All 행과 All 열이 추가되어 단일 줄 안에서 그룹 통계를 얻을 수 있음. 다음 예제에서 All 값은 흡연자와 비흡연자를 구분하지 않은 평균 값(All 열)이거나 행에서 두 단계를 묶은 그룹의 평균 값(All 열).
aggfunc에 다른 집계 함수인 'count'나 'len' 함수는 그룹 크기의 교차일람표(총 개수나 빈도)를 반환.
어떤 조합이 비어있거나 NA 값이라면 fill_value를 넘길 수 있음.
표 9-2 pivot_table 옵션 values: 집계하려는 칼럼 이름 또는 이름의 리스트. 기본 값으로 모든 숫자 칼럼을 집계 rows: 피벗 테이블의 행으로 묶을 칼럼 이름이나 그룹 키 cols: 피벗 테이블의 열로 묶을 칼럼 이름이나 그룹 키 aggfunc: 집계 함수나 함수 리스트. 기본 값은 'mean'이고 groupby 컨텍스트 안에서 유효한 어떤 함수라도 가능 fill_value: 누락된 값을 대체하기 위한 값 margins: 부분합이나 총계를 담기 위한 행/열을 추가할지의 여부. 기본 값은 False
교차일람표는 그룹 빈도를 계산하기 위한 피벗 테이블의 특수한 경우.
crosstab 함수의 처음 두 인자는 배열이나 Series, 또는 배열의 리스트가 될 수 있음.
미국 연방 선거관리위원회의 정치 활동 후원금 공개 데이터에는 기부자의 이름, 직업, 고용 형태, 주소, 기부 금액이 포함되어 있음. 그중 2012년 미국 대통령 선거 데이터에는 2012년 6월 현재 모든 주를 포함하는 전체 데이터는 150메가바이트의 CSV 파일임.
기부자와 선거자금에서 찾을 수 있는 패턴에 대한 통계를 추출하기 위해 적당한 크기로 쪼개서 나누는 다양한 방법이 있음. 여기에는 정당 가입 여부에 대한 데이터가 없으므로 추가해 주는 것이 좋음. uniq 메서드를 이용해 모든 정당의 후보 목록을 얻자.
소속 정당을 표시한 사전을 이용해서 Series 객체에 map 메서드를 통해 후보 이름으로부터 정당 배열을 계산해 낼 수 있다.
직업에 따른 기부 내역 통계에서 변호사는 민주당에 더 많은 돈을 기부하는 경향이 있으며 기업 임원은 공화당에 더 많은 돈을 기부하는 경향이 있는데, 이를 데이터를 통해 직접 확인해 보자.
내용을 보면 일반적인 직업 유형이나 유형은 같지만 이름이 다른 결과가 많이 포함되어 있음. 다음 코드를 이용해서 하나의 직업을 다른 직업으로 매핑함으로써 이런 몇몇 문제를 제거하자. dict.get을 사용하여 매핑 정보가 없는 직업은 그대로 사용한다. 고용주에도 같이 적용한다.
pivot_table을 사용해서 정당과 직업별로 데이터를 집계한 다음 최소한 2백만 불 이상 기부한 직업만 골라내자.
이런 종류의 데이터는 막대 그래프(barth: 수평 막대 그래프)를 통해 시각화 하는 편이 좋다.
오바마나 롬니 후보별로 가장 많은 금액을 기부한 직군을 알아보자. 이를 위해 후보 이름으로 그룹을 묶고 변형된 top 메서드를 사용하면 된다.
직업과 피고용에 따라 집계함.
cut 함수를 사용해서 기부 규모별로 버킷을 만들어 기부자 수를 분활화 함으로써 효과적으로 분석.
이제 기부자 이름과 버킷 이름으로 그룹을 묶어 기부 규모의 금액에 따른 히스토그램을 그릴 수 있음.
위 데이터를 보면 오바마는 룸니보자 적은 금액의 기부를 훨씬 많이 받았음. 기부 금액을 모두 더한 후 버킷별로 정규화 해서 후보별 전체 기부 금액 대비 비율을 시각화 할 수 있다.
다른 적용 가능 예: 기부자의 이름과 우편번호를 이용해서 적은 금액을 자주 기부한 사람과 많은 금액을 기부한 사람별로 데이터를 집계.
각 행을 전체 기부금액별로 나누면 각 후보에 대해 주별로 전체 기부금액 대비 상대적인 비율을 얻을 수 있음.