🟡 pandas 2차 강의 🟡
✅ 그룹화
🔰 .groupby() - 그룹화(Grouping)
- DataFrame.groupby()
- 데이터 분석에서 매우 중요한 개념으로, 어떤 키를 기준으로 데이터를 묶은 뒤 각 그룹에 대한 통계치를 계산하는 작업
- SQL의 GROUP BY 절과 유사한 기능
- 그룹화를 한 후 합계(sum), 평균(mean), 개수(count) 등 다양한 집계(aggregation) 함수 적용 가능
단일 열로 그룹화
- ex) 팁(tips) 데이터에서 **요일(day)**별로 팁 평균 구하기. 그룹 키는 'day', 집계 함수는 평균(mean)을 사용
import seaborn as sns
tips = sns.load_dataset('tips')
mean_tips_by_day = tips.groupby('day')['tip'].mean()
print(mean_tips_by_day)
*>>> 출력
day
Thur 2.771452
Fri 2.734737
Sat 2.993103
Sun 3.255132
Name: tip, dtype: float64*
- 결과 형태 : groupby를 하고 나서 한 컬럼에 대해 집계(mean)까지 하면 Series 객체로 결과가 나옵니다. 만약 여러 컬럼에 대해 집계했다면 DataFrame으로 반환됩니다.
- 여러 통계 한번에 :
- 만약 각 그룹에 대해 여러 가지 통계를 한 번에 보고 싶다면 **agg()**메서드 사용
- 예시) 요일별 팁의 평균과 합계를 모두 알고 싶다
tips.groupby('day')['tip'].agg(['mean', 'sum'])
*>>> 출력
mean sum
day
Thur 2.771452 171.83
Fri 2.734737 51.96
Sat 2.993103 260.40
Sun 3.255132 247.39*
다중 열로 그룹화
- 그룹 키를 2개 이상 지정하면 **계층적 그룹(hierarchical grouping)**이 이루어짐
- 예시) **요일별(day) 그리고 흡연여부별(smoker)**로 그룹을 나눈 뒤 각 그룹의 행 개수(식사 건수) 구하기
grouped = tips.groupby(['day', 'smoker'])['tip'].count()
print(grouped) # MultiIndex를 갖는 Series
*****>>> 출력
day smoker
Thur Yes 17
No 45
Fri Yes 15
No 4
Sat Yes 42
No 45
Sun Yes 19
No 57*
print(grouped.reset_index(name='count'))
*>>> 출력
day smoker count
0 Thur Yes 17
1 Thur No 45
2 Fri Yes 15
3 Fri No 4
4 Sat Yes 42
5 Sat No 45
6 Sun Yes 19
7 Sun No 57*
print(grouped.unstack())
*>>> 출력
smoker Yes No
day
Thur 17 45
Fri 15 4
Sat 42 45
Sun 19 57*
- MultiIndex 결과 :
- 두 개의 그룹키를 사용하면 인덱스가 두 단계(MultiIndex)로 나타남
- 위 예시의 **grouped**를 그대로 출력한 결과smoker가 Yes No 인덱스 레벨로 나타남
- .unstack() : 2차원 형태로 펼침 (피벗과 유사)
- .reset_index() : 인덱스를 일반 컬럼으로 돌림
.agg() - 여러 집계 함수 사용
- agg()에 딕셔너리를 사용하면 컬럼마다 다른 집계 함수를 적용할 수 있음
- .agg(['mean','max','min'])처럼 한 컬럼에 여러 함수 적용 가능
- 예시) 요일별로 total_bill과 tip의 합계와 평균을 동시에 계산하려면:
tips.groupby('day').agg({
'total_bill': 'sum',
'tip': 'mean'
})
*>>> 출력
total_bill tip
day
Thur 1096.33 2.771452
Fri 325.88 2.734737
Sat 1778.40 2.993103
Sun 1627.16 3.255132*
✅ 데이터 처리
🔰 문자열 처리
Series.str 접근자를 통해 이용
주요 문자열 메서드
- Series.str.lower(), upper(), title(): 문자열의 대소문자 변환 (모두 소문자, 모두 대문자, 단어마다 첫 글자만 대문자)
- Series.str.strip(): 좌우 공백 제거 (특정 문자 제거는 .strip('x'))
- Series.str.contains('문자열'): 각 문자열이 특정 패턴을 포함하는지 True/False
- Series.str.replace('패턴', '대체'): 문자열 치환 (정규식 패턴 사용 가능, 정규식 아닌 단순 치환은 regex=False 지정)
- Series.str.split('구분자'): 구분자로 분할 -> 리스트 반환 (이후 .str[0]으로 첫 부분 추출 등 가능)
- Series.str.cat(sep=','): 문자열 연결 (리스트처럼 이어붙이기)
- Series.str.len(): 문자열 길이 반환 (숫자에는 NaN)
이 외에도 부분 문자열 추출 (slice), 정규표현식 extract, startswith, endswith, find 등등
- 예시)
import pandas as pd
products = pd.Series([
"Bush Somerset Collection Bookcase",
"Hon Deluxe Fabric Upholstered Stacking Chairs, Rounded Back",
"Self-Adhesive Address Labels",
"Staples 8.5x11 Copy Paper"
])
print(products)
*>>> 출력
0 Bush Somerset Collection Bookcase
1 Hon Deluxe Fabric Upholstered Stacking Chairs,...
2 Self-Adhesive Address Labels
3 Staples 8.5x11 Copy Paper*
str.contains()
- 부분 문자열 검색
- 반환값 True/False
- ex) 상품명에 "Bookcase"라는 단어가 들어가는지를 찾고 싶다면
contains_bookcase = products.str.contains("Bookcase")
print("Contains 'Bookcase':", contains_bookcase.tolist())
>>>
Contains 'Bookcase': [True, False, False, False]
str.replace()
- 문자열 치환
- ex) 상품명에서 공백을 밑줄(_)로 바꾸고 싶다면:
underscored = products.str.replace(" ", "_", regex=False)
print(underscored)
*>>> 출력
0 Bush_Somerset_Collection_Bookcase
1 Hon_Deluxe_Fabric_Upholstered_Stacking_Chairs,...
2 Self-Adhesive_Address_Labels
3 Staples_8.5x11_Copy_Paper*
⇒
regex=False를 준 것은 공백문자 ' '를 정규식이 아닌 리터럴로 처리하기 위해서
정규식 패턴 사용할 수도 있음 (예: str.replace(r"\s+", "_")는 연속 공백도 하나의 밑줄로 치환 등).
str.upper() / str.title()
- products.str.upper() : 모든 글자를 대문자
- products.str.lower() : 모든 글자를 소문자
- title() : 각 단어가 Capitalize
print(products.str.title())
>> 출력
*0 Bush Somerset Collection Bookcase
1 Hon Deluxe Fabric Upholstered Stacking Chairs,...
2 Self-Adhesive Address Labels
3 Staples 8.5X11 Copy Paper*
str.split()
- 분할과 추출
- 문자열을 원하는 구분자로 쪼개어 list를 반환
- 만약 특정 패턴 앞부분만 추출하고 싶다면 str.split 후 리스트의 0번 요소를 가져오면 됨
brands = products.str.split().str[0]
print(brands)
>>>
0 Bush
1 Hon
2 Self-Adhesive
3 Staples
- 정규 표현식 활용(선택)-무시 가능!
- 판다스 문자열 메서드는 contains, replace, extract 등에서 정규식 사용 ⭕
- ex) 제품명에 숫자가 들어있는지 확인하려면 정규식 \\d (숫자)로 contains
- 결과가 [False, False, False, True] 로 출력되어 마지막 제품명 "Staples 8.5x11 Copy Paper"에만 숫자가 있음을 알 수 있음
has_digit = products.str.contains(r"\\d")
print(has_digit.tolist())
*>>> [False, False, False, True]*
🔰 시간 데이터 처리
NumPy의 datetime64 타입을 기반으로, **Timestamp(타임스탬프)**와 DatetimeIndex(시간 인덱스) 등을 사용해 시계열 데이터를 효율적으로 처리
- 날짜 형식 문자열을 datetime 객체로 변환
- DatetimeIndex를 인덱스로 사용하는 시계열
- 날짜/시간 속성 (년도, 월, 요일 등) 추출
날짜 형식 변환 (pd.to_datetime)
- 일반적으로 CSV나 Excel에서 읽은 날짜는 문자열로 들어오는 경우 다수.
- 이를 판다스 datetime 객체로 변환해야 연산이나 비교가 제대로 가능.
- pd.to_datetime() 함수를 사용하면 문자열을 날짜/시간으로 파싱
- to_datetime은 형식을 자동 인식하려 하지만, 실패할 경우 format 인자로 형식을 지정해 줄 수도 있음 (예: format='%d/%m/%Y').
dates = pd.to_datetime(orders['Order Date'], format='%d/%m/%Y')
print(dates.head())
*>>>
0 2017-11-08
1 2017-11-08
2 2017-06-12
3 2016-10-11
4 2016-10-11
Name: Order Date, dtype: datetime64[ns]*
DatetimeIndex와 시계열
orderstime = orders.set_index('Order Date')
인덱스가 시간으로 바뀌고, 정렬도 시간 순으로 자동 정렬되며, 기간 선택도 용이해짐 (orders['2016']처럼 연도 문자열로 슬라이싱 가능 등).
날짜/시간 속성 접근 (dt 접근자)
Series가 datetime64 타입인 경우 Series.dt를 통해 각 날짜의 구성 요소를 뽑을 수 있음
- Series.dt.year, month, day, hour, minute 등: 연, 월, 일, 시, 분...
- Series.dt.day_name(): 요일 이름 (예: Monday)
- Series.dt.weekday: 요일 번호 (월=0,...일=6)
- Series.dt.quarter: 분기(1~4)
- Series.dt.days_in_month: 그 달의 일 수 등.
- ex) Superstore 주문 데이터에 Order_Date 컬럼에서 연도와 요일을 새 컬럼으로 추출
orders = pd.read_csv("superstore_orders.csv")
dates = pd.to_datetime(orders['Order Date'], format='%d/%m/%Y')
orders['Year'] = dates.dt.year
orders['Weekday'] = dates.dt.day_name()
print(orders[['Order Date','Year','Weekday']].head(3))
*>>>
Order Date Year Weekday
0 08/11/2017 2017 Wednesday
1 08/11/2017 2017 Wednesday
2 12/06/2017 2017 Monday*
✅ 데이터 결합
🔰 .merge()
- pd.merge(df1, df2, how='inner', on='키')
- 두 데이터프레임의 공통 컬럼을 키로 삼아 조인
- how 인자는 SQL의 JOIN과 마찬가지로 'inner', 'left', 'right', 'outer' 등을 지정 가능
- 만약 키 컬럼 이름이 다르면 left_on과 right_on을 각각 지정해줄 수 있음. 혹은 인덱스를 키로 쓰고 싶으면 left_index=True, right_index=True 옵션을 사용
- ex)
- 슈퍼스토어 데이터에는 Orders 테이블과 Returns 테이블이 있다고 가정하겠습니다.
- Orders에는 모든 주문의 내역이 있고, Returns에는 반품된 주문의 ID 목록이 있습니다.
- 반품 여부를 주문 데이터에 합치고 싶을 때, 두 테이블을 Order ID로 merge 하면 됩니다.
orders = pd.DataFrame({
'Order ID': [101, 102, 103],
'Product': ['Bookcase', 'Chair', 'Lamp'],
'Profit': [100, 50, 20]
})
returns = pd.DataFrame({
'Order ID': [102, 105],
'Returned': ['Yes', 'Yes']
})
merged = orders.merge(returns, on='Order ID', how='left')
merged['Returned'] = merged['Returned'].fillna('No')
print(merged)
*>>>
Order ID Product Profit Returned
0 101 Bookcase 100 No
1 102 Chair 50 Yes
2 103 Lamp 20 No*
🔰 .concat()
- pd.concat([df1, df2, ...], axis=0)
- 컬럼이 동일한 둘 이상의 데이터프레임을 위아래로 이어붙임
- 인덱스가 겹칠 수 있으니 무시하려면 ignore_index=True 옵션
- axis=1로 지정하면 열 방향으로 옆으로 붙입니다. 이 경우 행 인덱스 기준으로 정렬되어 붙고, 맞지 않는 인덱스엔 NaN이 들어갑니다.
- ex) 4대 도시 매출 데이터가 각각 별도 데이터프레임으로 주어졌을 때, 이를 하나로 합칠 수 있습니다:
df1 = pd.DataFrame({'City': ['Seoul','Busan'], 'Sales':[100,150]})
df2 = pd.DataFrame({'City': ['Daegu','Incheon'], 'Sales':[80,70]})
combined = pd.concat([df1, df2], ignore_index=True)
print(combined)
>>>
City Sales
0 Seoul 100
1 Busan 150
2 Daegu 80
3 Incheon 70
✅ apply, map, lambda
🔰 .apply
- 데이터프레임이나 시리즈의 각 원소 또는 각 행/열에 임의의 함수를 적용할 수 있는 apply 계열 메서드를 제공
- DataFrame.apply(func, axis=0) : 각 열(column)에 대해 함수를 적용 (default). axis=1로 지정하면 각 행(row)에 대해 함수를 적용. 결과는 함수 반환에 따라 Series 또는 DataFrame이 될 수 있음.
- Series.apply(func) : Series의 각 값에 함수를 적용한 결과 Series를 반환 (element-wise 적용)
- ex)
- tips 데이터에서 각 행에 대해 팁 비율 (tip_pct = tip/total_bill)을 계산해 새로운 열로 추가
- 벡터화 연산으로 tips['tip'] / tips['total_bill'] 이렇게 하면 빠르지만, 여기서는 apply를 활용
tips['tip_pct'] = tips.apply(lambda row: row['tip'] / row['total_bill'], axis=1)
print(tips[['total_bill','tip','tip_pct']].head())
*>>>
total_bill tip tip_pct
0 16.99 1.01 0.059447
1 10.34 1.66 0.160542
2 21.01 3.50 0.166587
3 23.68 3.31 0.139780
4 24.59 3.61 0.146808*
각 행을 입력으로 받아 람다 함수에서 tip/total_bill을 계산해 반환한 결과가 tip_pct 열로 저장되었습니다. 위에서 0번 손님의 팁 비율이 약 5.95%, 2번 손님은 약 16.67% 등으로 나타났습니다.
- 이 방식은 tips['tip'] / tips['total_bill']처럼 벡터화 연산으로 바로 계산하는 것에 비해 속도가 느릴 수 있지만, 임의의 복잡한 계산을 하는 경우 apply를 쓰면 간편합니다.
- axis=1을 빼먹으면 기본 axis=0이라 컬럼별 작동하려고 해서 KeyError가 나니 주의합니다.
- 열 기준 apply: axis=0 일 때, 전달되는 인자는 각 열을 Series로 본 함수입니다. 예를 들어 df.apply(np.mean)은 각 컬럼의 평균으로 이루어진 Series를 돌려줍니다. 또는 df.apply(lambda col: col.max() - col.min())는 각 컬럼의 range(최댓값-최솟값)를 계산해 줄 수 있습니다
🔰 .map
- Series.map(func or dict)
- Series의 각 요소를 주어진 함수에 매핑하여 새로운 Series를 반환. 단, map은 원소별 동작만 가능하고 index나 다른 컬럼 정보는 접근❌ (그래서 행 종합 처리는 apply(axis=1) 사용).
- map에는 함수 대신 딕셔너리나 Series를 넣어서 값 대체(lookup) 용도로도 많이 사용함
- ex) 'sex' 컬럼이 Male/Female로 되어 있을 때 이를 1/0으로 바꾸는 'gender_code' 컬럼 추가:
tips['gender_code'] = tips['sex'].map({'Male': 1, 'Female': 0})
print(tips[['sex','gender_code']].head(3))
*>>>
sex gender_code
0 Female 0
1 Male 1
2 Male 1*
- ex) total_bill 금액이 20달러 이상이면 'expensive' 아니면 'cheap'이라는 레이블 생성
tips['price_label'] = tips['total_bill'].map(lambda x: 'expensive' if x >= 20 else 'cheap')
print(tips[['total_bill','price_label']].head(5))
>>>
total_bill price_label
0 16.99 cheap
1 10.34 cheap
2 21.01 expensive
3 23.68 expensive
4 24.59 expensive
apply/map 사용 시 주의 (성능)
apply, map 등은 파이썬 레벨 함수를 각 원소에 적용하기 때문에 벡터화 연산보다 느릴 수 있습니다. 가능하면 판다스의 기본 연산이나 문자열 메서드 등 벡터화 기능을 활용하고, 정말 커스텀 로직이 필요할 때만 apply를 사용하세요.
예를 들어 위 tip_pct 계산은 tips['tip']/tips['total_bill']처럼 바로 계산하는 것이 apply(lambda)보다 수십 배 빠릅니다.
Series.map은 내부적으로 C로 구현된 딕셔너리 매핑 등을 쓰므로 비교적 빠른 편이지만, 복잡한 함수면 느려질 수 있습니다.
lambda와 함께 쓰는 경우
lambda (익명 함수)는 간단한 함수식을 일회용으로 사용할 때 편리합니다. 위 예시들처럼 사용했고, 물론 미리 정의한 함수명을 넣어도 됩니다. Python의 lambda는 lambda x: <표현식> 형태로 쓰며, 표현식 결과가 반환됩니다.
정리: apply, map은 판다스에 익숙해질수록 요긴하게 쓰이는 도구입니다. 특히 데이터 가공이나 피처 엔지니어링 단계에서 다양한 변형을 할 때 활용됩니다. 다만, 사용하는 함수의 구현에 따라 성능에 영향을 주니 큰 데이터셋에서는 주의해야 합니다.
🔰 과제
실습
아티클
💡 오늘의 아티클 (주제)
주제 : 넷플릭스와 아마존은 데이터 분석을 어떻게 할까요?
- 요약
- 사용자 행동 데이터 분석의 중요성과 실제 기업 사례를 통한 비즈니스 개선 방안
- 주요 포인트
- 넷플릭스 : 콘텐츠 제작에 관한 여러 사용자 행동 데이터를 분석 후 도출된 인사이트를 바탕으로 특정 시리즈 제작에 투자하여 신규 가입자 발생 및 기존 유저 이탈률 감소
- 아마존 : 사용자 행동 데이터 분석 후 웹사이트 로딩과 판매량의 상관관계를 도출해 이를 바탕으로 로딩 속도 개선을 통해 평균 구매 전환율을 13% 달성
- 맞춤 광고 : 사용자의 검색/클릭 이력을 기반으로 맞춤형 광고를 제공함으로써 광고 효율성 극대화 및 비용 절감
- 핵심 개념
- 사용자 행동 데이터 (User Behavior Data) : 사용자가 서비스 내에서 행동한 대부분의 활동을 추적하는 데이터
- 구매 전환율 : 특정 기간 동안 웹사이트를 방문한 전체 사용자 중 실제 구매를 완료한 사용자의 비율
- 개인 인사이트
아마존의 웹사이트 로딩 0.1초 지연이라는 매우 사소해 보이는 미시적인 데이터가 판매 1% 감소라는 어마무시한 작용으로 이어진다는 것은 역시 나비 효과 이론이 괜히 나온게 아니다.고로 사용자 활동 데이터는 세밀하고도 다양한 관점으로 해석할 수 있어야 한다고 생각한다.
아무것도 아닐 것 같은 작은 개선, 즉 적은 인풋으로 큰 성과 - 극대화된 아웃풋- 를 이루어내는 것이 기업들이 가장 원하고도 바라는 이상적인 투자일 것이다.
'Sparta > Theory' 카테고리의 다른 글
| [250901] pandas visualizer (0) | 2025.09.01 |
|---|---|
| [250830] pandas (1) | 2025.09.01 |
| [250828] 스파르타코딩 본캠프 18일차 (1) - pandas 01 (2) | 2025.08.28 |
| [250825] 스파르타코딩 본캠프 15일차 (1) (1) | 2025.08.25 |
| [250822] 스파르타코딩 본캠프 14일차 (1) - Python 04 - dict, def (3) | 2025.08.22 |