Today I learned
1. QAQC 문제풀이 가이드
1) 중복된 행의 갯수 추출하기
import numpy as np
import pandas as pd
np.random.seed(123)
base_trades = pd.DataFrame({
'trade_id': range(1, 21),
'stock_symbol': np.random.choice(['AAPL', 'MSFT', 'GOOGL'], 20),
'trade_date': pd.date_range('2024-01-01', periods=20, freq='D'),
'quantity': np.random.randint(100, 1000, 20),
'price': np.round(np.random.uniform(100, 200, 20), 2)
})
duplicate_trades = base_trades.iloc[[2, 5, 8, 15]].copy()
duplicate_trades['trade_id'] = [101, 102, 103, 104]
exact_duplicate = base_trades.iloc[[10, 12]].copy()
trades_df = pd.concat([base_trades, duplicate_trades, exact_duplicate], ignore_index=True)
count_trade = trades_df[trades_df.duplicated(keep=False)]
print(len(count_trade.drop_duplicates()))
2
#count_trade 변수를 통해 중복된 행들을 찾아니고, .drop_duplicates()로 중복된 항목을 제거한 후 중복된 항목이 있었던 최초 갯수들만의 길이를 합해서 print
2) 중복된 행 제거하기
import numpy as np
import pandas as pd
np.random.seed(123)
base_trades = pd.DataFrame({
'trade_id': range(1, 21),
'stock_symbol': np.random.choice(['AAPL', 'MSFT', 'GOOGL'], 20),
'trade_date': pd.date_range('2024-01-01', periods=20, freq='D'),
'quantity': np.random.randint(100, 1000, 20),
'price': np.round(np.random.uniform(100, 200, 20), 2)
})
duplicate_trades = base_trades.iloc[[2, 5, 8, 15]].copy()
duplicate_trades['trade_id'] = [101, 102, 103, 104]
exact_duplicate = base_trades.iloc[[10, 12]].copy()
trades_df = pd.concat([base_trades, duplicate_trades, exact_duplicate], ignore_index=True)
print(len(trades_df.drop_duplicates()))
24
# trades_df 데이터 프레임에서 .drop_duplicates()로 중복된 행을 제거함, 그 길이를 구한 값을 print
3) subset을 통한 중복행 제거하기
import numpy as np
import pandas as pd
np.random.seed(123)
base_trades = pd.DataFrame({
'trade_id': range(1, 21),
'stock_symbol': np.random.choice(['AAPL', 'MSFT', 'GOOGL'], 20),
'trade_date': pd.date_range('2024-01-01', periods=20, freq='D'),
'quantity': np.random.randint(100, 1000, 20),
'price': np.round(np.random.uniform(100, 200, 20), 2)
})
duplicate_trades = base_trades.iloc[[2, 5, 8, 15]].copy()
duplicate_trades['trade_id'] = [101, 102, 103, 104]
exact_duplicate = base_trades.iloc[[10, 12]].copy()
trades_df = pd.concat([base_trades, duplicate_trades, exact_duplicate], ignore_index=True)
trades_df1=trades_df.drop_duplicates(subset = ['stock_symbol','trade_date'], keep = 'last')
print(trades_df1)
# subset을 적용하여 'stock_symbol'컬럼과 'trade_date' 컬럼을 기준으로 중복 여부를 판단함. 즉 stock_symbol과 trade_date가 똑같으면 중복으로 간주한다는 것.
# keep = last는 중복된 행 중 마지막만 남기고 나머지만 삭제함
4) 라인플롯(서브플롯 방식)
import matplotlib.pyplot as plt
x=[1,2,3,4,5]
y=[10,12,15,13,18]
fig, ax = plt.subplots()
ax.plot(x,y, label = 'Series A', linestyle = '--', marker = 'o')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_title('Line Plot Example')
ax.legend()
print(len(ax.lines))
plt.show()
# ax.plot 절에서 점선, o 표시를 입력함
# ax.lines은 그려진 모든 선 객체의 리스트를 의미함. 즉 리스트의 길이를 측정하면 1이 나오는게 당연함.
5) 박스플롯
import pandas as pd
import matplotlib.pyplot as plt
# DataFrame 생성
df = pd.DataFrame({
'A': [10, 12, 9, 11, 13],
'B': [20, 22, 21, 19, 23],
'C': [30, 28, 31, 29, 27],
'D': [40, 42, 39, 41, 43]
})
data = df[['B','D']]
fig, ax = plt.subplots()
ret = ax.boxplot([data['B'],data['D']], labels = ['B','D'])
ax.set_title('Box Plot Example')
plt.show()
print(len(ret['boxes']))
#ret: 박스플롯에서 그려진 박스 객체들이 들어있는 리스트
6) 바이올린 플롯
import pandas as pd
import matplotlib.pyplot as plt
# DataFrame 생성
df = pd.DataFrame({
'A': [5, 7, 6, 9, 8],
'B': [15, 14, 13, 16, 15],
'C': [25, 27, 26, 24, 28],
'D': [35, 36, 34, 33, 37]
})
data = df[['A', 'C']]
fig, ax = plt.subplots()
vp = ax.violinplot([data['A'], data['C']])
ax.set_title('Violin Plot Example')
print(len(vp['bodies']))
#bodies: 바이올린 도형 리스트, vp['bodies']에는 2개의 바이올린 모양 객체가 들어있음
2. (라이브 세션) 파이썬 라이브러리 세션 4회차
1) 데이터 분석의 5단계
| 단게 |
개념/목적 |
핵심 활동 방법 |
예시/주의사항 |
결과 |
1단계: 문제 정의 |
문제를 명확하고 측정 가능하게 설정 |
- 5W1H 구조화 - 이해관계자 인터뷰 - 기존 보고서·데이터 검토 - 중요도/영향도 기준 우선순위 결정 |
예시: 교육 참여율 35% → 목표 50% |
문제 정의서, KPI |
2단계: 가설 설정 |
문제 원인·해결 방향에 대한 잠정적 설명 제시 |
- 브레인스토밍 - MECE 원칙 적용 - 가설 매트릭스 작성 |
가설 유형: ① 원인: 근무시간과 겹침 ② 상관관계: 만족도 ↔ 참여율 ③ 차이: 부서별 차이 |
가설 목록, 검증 계획 |
3단계: 데이터 수집 |
가설 검증에 필요한 데이터 확보·정리 |
- 내부(HR, 근태, 로그) - 외부(벤치마킹) - 정성(인터뷰) - 정량(참여율 등) - 품질 관리(정확·완전·일관·적시) - 수집 방법(자동화/수동/기존 시스템) - 전처리(결측치, 이상치, 변환, 파생변수) |
예시: LMS 로그, HR 시스템 데이터, 직원 만족도 조사 |
정제된 데이터셋 |
4단계: 가설 검증 |
데이터를 통한 가설의 참/거짓 검증 |
- EDA (탐색적 분석) - 통계 검정 (t-test, χ², ANOVA 등) - 상관/회귀 분석 - 필요시 고급기법(머신러닝, 시계열) |
주의사항: -다중 검정 오류 - 충분한 표본 확보 - 통계적 가정 충족 - 상관 ≠ 인과 |
검정 결과, 통계 요약, 시각화 |
5단계: 결론 및 인사이트
|
결과 종합 및 실행 전략 도출 |
- 가설 채택/기각 정리 - 핵심 발견·패턴·트렌드 도출 - 예외적 결과 분석 - 스토리텔링 보고 - 시각화(대시보드, 차트, 인포그래픽) - 실행 계획(로드맵, KPI, 피드백 루프) |
좋은 인사이트 조건: - Actionable - Novel - Relevant - Reliable |
인사이트 보고서, 실행 로드맵 |
2) 예시 데이터
World Happiness Report 2019
3) 기술통계 분석
import pandas as pd
df = pd.read_csv("2019.csv")
#우선적으로 판다스를 선언하고 데이터프레임을 만들자.
df.columns
Index(['Overall rank', 'Country or region', 'Score', 'GDP per capita', 'Social support', 'Healthy life expectancy', 'Freedom to make life choices', 'Generosity', 'Perceptions of corruption'], dtype='object')
# 컬럼에 뭐가 있나 확인
print("기본 요약 통계")
print(df[[
'Score',
'GDP per capita',
'Social support',
'Healthy life expectancy',
'Freedom to make life choices',
'Generosity',
'Perceptions of corruption']
].describe())
| 지표 |
count |
mean |
std |
min |
25% |
50% |
75% |
max |
| Score |
156.0 |
5.407 |
1.113 |
2.853 |
4.545 |
5.380 |
6.185 |
7.769 |
| GDP per capita |
156.0 |
0.905 |
0.398 |
0.000 |
0.603 |
0.960 |
1.233 |
1.684 |
| Social support |
156.0 |
1.209 |
0.299 |
0.000 |
1.056 |
1.272 |
1.453 |
1.624 |
| Healthy life expectancy |
156.0 |
0.725 |
0.242 |
0.000 |
0.548 |
0.789 |
0.882 |
1.141 |
| Freedom to make life choices |
156.0 |
0.393 |
0.143 |
0.000 |
0.308 |
0.417 |
0.507 |
0.631 |
| Generosity |
156.0 |
0.185 |
0.095 |
0.000 |
0.109 |
0.178 |
0.248 |
0.566 |
| Perceptions of corruption |
156.0 |
0.111 |
0.095 |
0.000 |
0.047 |
0.086 |
0.141 |
0.453 |
#describe로 각 지표의 세부 사항을 볼 수 있음
#최빈값
print("\n최빈값:")
print(df[[
'Score',
'GDP per capita',
'Social support',
'Healthy life expectancy',
'Freedom to make life choices',
'Generosity',
'Perceptions of corruption']
].mode().iloc[0])
지표 최빈값
| Score |
5.208 |
| GDP per capita |
0.960 |
| Social support |
1.465 |
| Healthy life expectancy |
0.815 |
| Freedom to make life choices |
0.498 |
| Generosity |
0.153 |
| Perceptions of corruption |
0.028 |
#.mode(): 각 열 별로 최빈값을 계산하여 dataframe을 반환
#.iloc[0]: 행 단위 인덱싱,
최빈값이 여러개가 나왔을 때 첫번째 행(입력한 [0]값, 두번째 행이면 [1])의 값만 가져오겠다
# 빈도분석
df["Score_Group"] = pd.qcut(df['Score'], q = 2, labels = ["낮아용", "높아용"])
df
# pd.qcut: 데이터를 분위수 기준으로 구간을 나눠줌
#해당 코드의 경우 상위 50% 은 높아용, 하위 50%는 낮아용이 표기
df['Score_Group'].value_counts()
Score_Group
낮아용 78
높아용 78
Name: count, dtype: int64
#딱 정확하게 반으로 갈라진 것을 볼 수 있음, 2구간으로 나눈 분위수이기 때문!
df.groupby('Score_Group')['GDP per capita'].mean()
Score_Group
낮아용 0.634974
높아용 1.175321
Name: GDP per capita, dtype: float64
# 각 분위 수의 GDP per capita 컬럼 데이터의 평균
3. (라이브 세션) [데이터 전처리 & 시각화] 고오급 시각화
1) 데이터 집계 및 재구조화
Groupby: 데이터를 특정 기준에 따라 그룹으로 묶어서 집계나 변환할 때 사용하는 기
import pandas as pd
simple_data = {
'region': ['Seoul', 'Seoul', 'Busan', 'Seoul', 'Busan', 'Busan'],
'product': ['A', 'B', 'A', 'A', 'B', 'A'],
'sales': [100, 150, 80, 120, 110, 90]
}
df = pd.DataFrame(simple_data)
print("기본 데이터:")
df
| |
region |
product |
sales |
| 0 |
Seoul |
A |
100 |
| 1 |
Seoul |
B |
150 |
| 2 |
Busan |
A |
80 |
| 3 |
Seoul |
A |
120 |
| 4 |
Busan |
B |
110 |
| 5 |
Busan |
A |
90 |
#간단한 데이터 프레임을 만들었삼
grouped = df.groupby('region')
print("GroupBy 객체:", type(grouped))
GroupBy 객체: <class 'pandas.core.groupby.generic.DataFrameGroupBy'>
#grouped 변수는 위의 데이터프레임을 region 컬럼 값 별로 묶어 만든 그룹 객체를 담고 있음
# 각 그룹 확인하기
for name, group in grouped:
print(f"\n{name} 그룹:")
print(group)
| |
region |
product |
sales |
| 2 |
Busan |
A |
80 |
| 4 |
Busan |
B |
110 |
| 5 |
Busan |
A |
90 |
| 0 |
Seoul |
A |
100 |
| 1 |
Seoul |
B |
150 |
| 3 |
Seoul |
A |
120 |
#region 값 별로 분류가 된 것을 볼 수 있다
# 지역별 매출 평균
region_mean = df.groupby('region')['sales'].mean()
print("지역별 평균 매출:")
region_mean
region
Busan 93.333333
Seoul 123.333333
Name: sales, dtype: float64
#주의사항
df.groupby('region').sum()
| region |
product |
sales |
| Busan |
ABA |
280 |
| Seoul |
ABA |
370 |
#특정 컬럼을 선택하지 않고 groupby를 적용하면 문자열이 합쳐지는 불상사가 발생
df.groupby('region')['sales'].sum()
#이렇게 써 줘야 한다!
- agg 함수
여러 통계량을 한번에 적용 할 수 있다.
# 한 컬럼에 여러 함수 적용
region_agg = df.groupby('region')['sales'].agg(['sum', 'mean', 'count']) # .sum, .mean 넣던 것과는 달리 한번에 넣을 수 있음
print("지역별 종합 통계:")
region_agg
summeancountregionBusanSeoul
| |
sum |
mean |
count |
| region |
|
|
|
| busan |
280 |
93.333333 |
3 |
| seoul |
370 |
123.333333 |
3 |
# groupby의 결과로 region을 인덱스로 사용했기에 빈칸이 나옴
# groupby의 결과로 region 별 분류된 데이터
#한 칼럼에 여러 함수 적용
region_agg = df.groupby('region')['sales'].agg(['sum','mean','count'])
print("지역별 합계 총계")
region_agg
| |
sum |
mean |
count |
| region |
|
|
|
| Busan |
280 |
93.333333 |
3 |
| Seoul |
370 |
123.333333 |
3 |
#여러 칼럼에도 적용된다
2) 데이터 결합
- concat()
동일한 구조를 연결함
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})
print("첫 번째 데이터:")
print(df1)
print("두 번째 데이터:")
print(df2)
첫 번째 데이터:
A B
0 1 3
1 2 4
두 번째 데이터:
A B
0 5 7
1 6 8
#concat 기본 문법
pd. concat([데이터1, 데이터2], axis = 0, keys = ['이름1','이름2']
#axis가 0이면 세로로 연결, axis =1 일 때 가로로 연결
- merge
공통 키를 기준으로 데이터를 결합
# merge 기본 문법
# df1.merge(df2, on='공통컬럼', how='join방식')
# 핵심 파라미터:
# - on: 결합 기준이 되는 공통 컬럼
# - how: 결합 방식 ('left', 'right', 'inner', 'outer')# - left_on, right_on: 서로 다른 컬럼명으로 결합할 때
# JOIN 예제를 위한 간단한 데이터 생성
sales_sample = pd.DataFrame({
'product': ['Laptop', 'Phone', 'Speaker'],# Speaker는 product_info에 없음
'sales': [1000, 800, 300]
})
product_sample = pd.DataFrame({
'product': ['Laptop', 'Phone', 'Tablet'],# Tablet은 sales_sample에 없음
'number': [1500, 900, 600]
}
Sales_sample
| product |
sales |
| Laptop |
1000 |
| Phone |
800 |
| Speaker |
300 |
product_sample
| product |
number |
| Laptop |
1500 |
| Phone |
900 |
| Tablet |
600 |
#두개의 데이터 프레임을 만들었삼
a. INNER JOIN: 양쪽에 모두 있는 것들만
inner_result = sales_sample.merge(product_sample, on='product', how='inner')
print("INNER JOIN 결과:")
print(inner_result)
print("→ Laptop, Phone만 남음 (양쪽에 모두 존재)")
INNER JOIN 결과
| |
product |
sales |
number |
| 0 |
Laptop |
1000 |
1500 |
| 1 |
Phone |
800 |
900 |
#두개의 데이터 프레임에서 동일하게 있는 Laptop과 Phone 항이 결과로 도출
b. LEFT JOIN: 왼쪽 테이블 기준
left_result = sales_sample.merge(product_sample, on='product', how='left')
print("LEFT JOIN 결과:")
print(left_result)
print("→ sales_sample 모든 행 유지, Speaker는 price가 NaN")
LEFT JOIN 결과
| |
product |
sales |
number |
| 0 |
Laptop |
1000 |
1500 |
| 1 |
Phone |
800 |
900 |
| 2 |
Speaker |
300 |
NaN |
# 왼쪽 기준으로 나왔다. 오른쪽엔 speaker 데이터가 없으므로 NaN
c. RIGHT JOIN: 오른쪽 테이블 기준
right_result = sales_sample.merge(product_sample, on='product', how='right')
print("RIGHT JOIN 결과:")
print(right_result)
print("→ product_sample 모든 행 유지, Tablet은 sales가 NaN")
RIGHT JOIN 결과
| |
product |
sales |
number |
| 0 |
Laptop |
1000 |
1500 |
| 1 |
Phone |
800 |
900 |
| 2 |
Tablet |
NaN |
600 |
#오른쪽 기준, 왼쪽엔 Tablet 데이터가 없으므로 NaN
d. OUTER JOIN : 합집합
outer_result = sales_sample.merge(product_sample, on='product', how='outer')
print("OUTER JOIN 결과:")
print(outer_result)
print("→ 모든 제품 포함, 없는 값은 NaN")
OUTER JOIN 결과
| |
product |
sales |
number |
| 0 |
Laptop |
1000 |
1500 |
| 1 |
Phone |
800 |
900 |
| 2 |
Speaker |
300 |
NaN |
| 3 |
Tablet |
NaN |
600 |
# 모든 제품 포함, 없는 데이터는 NaN
3. Matplotlib 방식 - fig와 ax
- plt.subplots() 함수
이후 내용은 차후 복습 예정
4. 데이터 전처리 & 시각화 4주차