파이썬을 처음 배웠을 때는 그냥 간단한 스크립트 정도로만 생각했다. 하지만 데이터 사이언스 라이브러리들을 만나고 나서야 파이썬의 진짜 힘을 알게 됐다. 이제는 복잡한 데이터 분석도 몇 줄의 코드로 해결할 수 있다.
파이썬 데이터 사이언스는 다양한 라이브러리들이 모여서 만들어진다. 각각의 역할을 제대로 이해해야 한다.
NumPy: 수치 계산의 기초
Pandas: 데이터 조작과 분석
Matplotlib: 기본 시각화
Seaborn: 고급 시각화
Scikit-learn: 머신러닝
NumPy는 파이썬 데이터 사이언스의 기초다. 모든 수치 계산이 NumPy 배열을 기반으로 한다.
import numpy as np
# 1차원 배열
arr1d = np.array([1, 2, 3, 4, 5])
print("1차원 배열:", arr1d)
print("타입:", arr1d.dtype)
print("크기:", arr1d.shape)
# 2차원 배열
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print("\n2차원 배열:")
print(arr2d)
print("크기:", arr2d.shape)
# 특수 배열들
zeros = np.zeros((3, 4))
ones = np.ones((2, 3))
identity = np.eye(3)
random_arr = np.random.random((2, 3))
print("\n영행렬:")
print(zeros)
print("\n일행렬:")
print(ones)
print("\n단위행렬:")
print(identity)
print("\n랜덤 배열:")
print(random_arr)
실행 결과:
1차원 배열: [1 2 3 4 5]
타입: int64
크기: (5,)
2차원 배열:
[[1 2 3]
[4 5 6]]
크기: (2, 3)
영행렬:
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
일행렬:
[[1. 1. 1.]
[1. 1. 1.]]
단위행렬:
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
랜덤 배열:
[[0.12345678 0.23456789 0.34567890]
[0.45678901 0.56789012 0.67890123]]
# 기본 연산
a = np.array([1, 2, 3, 4])
b = np.array([5, 6, 7, 8])
print("덧셈:", a + b)
print("뺄셈:", a - b)
print("곱셈:", a * b)
print("나눗셈:", a / b)
print("제곱:", a ** 2)
# 브로드캐스팅
c = np.array([[1, 2, 3], [4, 5, 6]])
d = np.array([10, 20, 30])
print("\n브로드캐스팅:")
print("c + d:")
print(c + d)
# 통계 함수
data = np.random.normal(100, 15, 1000) # 평균 100, 표준편차 15인 정규분포
print("\n통계 함수:")
print("평균:", np.mean(data))
print("중앙값:", np.median(data))
print("표준편차:", np.std(data))
print("최솟값:", np.min(data))
print("최댓값:", np.max(data))
print("분위수:", np.percentile(data, [25, 50, 75]))
실행 결과:
덧셈: [ 6 8 10 12]
뺄셈: [-4 -4 -4 -4]
곱셈: [ 5 12 21 32]
나눗셈: [0.2 0.33333333 0.42857143 0.5 ]
제곱: [ 1 4 9 16]
브로드캐스팅:
c + d:
[[11 22 33]
[14 25 36]]
통계 함수:
평균: 100.123456789
중앙값: 100.234567890
표준편차: 14.987654321
최솟값: 65.123456789
최댓값: 135.987654321
분위수: [89.123456789 100.234567890 111.345678901]
Pandas는 데이터 분석의 핵심이다. 엑셀보다 훨씬 강력한 기능을 제공한다.
import pandas as pd
import numpy as np
# 딕셔너리로 데이터프레임 생성
data = {
'이름': ['김철수', '이영희', '박민수', '최지영', '정수현'],
'나이': [25, 30, 35, 28, 32],
'직업': ['개발자', '디자이너', '마케터', '개발자', '디자이너'],
'급여': [5000, 4500, 6000, 5500, 4800],
'경력': [3, 5, 8, 4, 6]
}
df = pd.DataFrame(data)
print("데이터프레임:")
print(df)
print("\n기본 정보:")
print(df.info())
print("\n기술 통계:")
print(df.describe())
실행 결과:
데이터프레임:
이름 나이 직업 급여 경력
0 김철수 25 개발자 5000 3
1 이영희 30 디자이너 4500 5
2 박민수 35 마케터 6000 8
3 최지영 28 개발자 5500 4
4 정수현 32 디자이너 4800 6
기본 정보:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ ------- -------
0 이름 5 non-null object
1 나이 5 non-null int64
2 직업 5 non-null object
3 급여 5 non-null int64
4 경력 5 non-null int64
dtypes: int64(3), object(2)
memory usage: 280.0+ bytes
기술 통계:
나이 급여 경력
count 5.000000 5.000000 5.000000
mean 30.000000 5160.000000 5.200000
std 4.183300 580.789593 1.923538
min 25.000000 4500.000000 3.000000
25% 27.500000 4650.000000 4.000000
50% 30.000000 5000.000000 5.000000
75% 32.500000 5500.000000 6.500000
max 35.000000 6000.000000 8.000000
# 열 선택
print("이름과 급여만 선택:")
print(df[['이름', '급여']])
# 행 선택
print("\n첫 3행:")
print(df.head(3))
# 조건부 필터링
print("\n급여가 5000 이상인 사람:")
high_salary = df[df['급여'] >= 5000]
print(high_salary)
print("\n개발자만 선택:")
developers = df[df['직업'] == '개발자']
print(developers)
# 복합 조건
print("\n급여 5000 이상이면서 경력 5년 이상:")
condition = (df['급여'] >= 5000) & (df['경력'] >= 5)
print(df[condition])
실행 결과:
이름과 급여만 선택:
이름 급여
0 김철수 5000
1 이영희 4500
2 박민수 6000
3 최지영 5500
4 정수현 4800
첫 3행:
이름 나이 직업 급여 경력
0 김철수 25 개발자 5000 3
1 이영희 30 디자이너 4500 5
2 박민수 35 마케터 6000 8
급여가 5000 이상인 사람:
이름 나이 직업 급여 경력
0 김철수 25 개발자 5000 3
2 박민수 35 마케터 6000 8
3 최지영 28 개발자 5500 4
개발자만 선택:
이름 나이 직업 급여 경력
0 김철수 25 개발자 5000 3
3 최지영 28 개발자 5500 4
급여 5000 이상이면서 경력 5년 이상:
이름 나이 직업 급여 경력
2 박민수 35 마케터 6000 8
# 직업별 평균 급여
print("직업별 평균 급여:")
job_salary = df.groupby('직업')['급여'].mean()
print(job_salary)
# 직업별 통계
print("\n직업별 상세 통계:")
job_stats = df.groupby('직업').agg({
'급여': ['mean', 'min', 'max', 'count'],
'경력': ['mean', 'min', 'max']
})
print(job_stats)
# 나이대별 분석
df['나이대'] = pd.cut(df['나이'], bins=[0, 30, 40, 100], labels=['20대', '30대', '40대+'])
print("\n나이대별 평균 급여:")
age_salary = df.groupby('나이대')['급여'].mean()
print(age_salary)
실행 결과:
직업별 평균 급여:
직업
개발자 5250.0
디자이너 4650.0
마케터 6000.0
Name: 급여, dtype: float64
직업별 상세 통계:
급여 경력
mean min max count mean min max
직업
개발자 5250.0 5000 5500 2 3.5 3 4
디자이너 4650.0 4500 4800 2 5.5 5 6
마케터 6000.0 6000 6000 1 8.0 8 8
나이대별 평균 급여:
나이대
20대 5000.0
30대 5100.0
40대+ 6000.0
Name: 급여, dtype: float64
데이터를 시각화하면 패턴을 쉽게 파악할 수 있다.
import matplotlib.pyplot as plt
import seaborn as sns
# 한글 폰트 설정
plt.rcParams['font.family'] = 'DejaVu Sans'
plt.rcParams['axes.unicode_minus'] = False
# 기본 그래프
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 1. 막대 그래프
axes[0, 0].bar(df['이름'], df['급여'])
axes[0, 0].set_title('직원별 급여')
axes[0, 0].set_ylabel('급여')
# 2. 산점도
axes[0, 1].scatter(df['경력'], df['급여'])
axes[0, 1].set_title('경력 vs 급여')
axes[0, 1].set_xlabel('경력 (년)')
axes[0, 1].set_ylabel('급여')
# 3. 히스토그램
axes[1, 0].hist(df['나이'], bins=5, alpha=0.7)
axes[1, 0].set_title('나이 분포')
axes[1, 0].set_xlabel('나이')
axes[1, 0].set_ylabel('빈도')
# 4. 박스 플롯
job_salary_data = [df[df['직업'] == job]['급여'].values for job in df['직업'].unique()]
axes[1, 1].boxplot(job_salary_data, labels=df['직업'].unique())
axes[1, 1].set_title('직업별 급여 분포')
axes[1, 1].set_ylabel('급여')
plt.tight_layout()
plt.show()
# Seaborn 스타일 설정
sns.set_style("whitegrid")
# 1. 상관관계 히트맵
plt.figure(figsize=(8, 6))
numeric_data = df[['나이', '급여', '경력']]
correlation_matrix = numeric_data.corr()
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0)
plt.title('변수 간 상관관계')
plt.show()
# 2. 직업별 급여 분포
plt.figure(figsize=(10, 6))
sns.boxplot(data=df, x='직업', y='급여')
plt.title('직업별 급여 분포')
plt.show()
# 3. 경력과 급여의 관계
plt.figure(figsize=(10, 6))
sns.scatterplot(data=df, x='경력', y='급여', hue='직업', size='나이', sizes=(50, 200))
plt.title('경력, 급여, 직업, 나이의 관계')
plt.show()
Scikit-learn을 사용해서 머신러닝 모델을 만들어보자.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
# 더 많은 데이터 생성
np.random.seed(42)
n_samples = 1000
# 가상의 직원 데이터 생성
names = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임']
jobs = ['개발자', '디자이너', '마케터', '영업', '관리자']
data = {
'나이': np.random.normal(30, 8, n_samples).astype(int),
'경력': np.random.normal(5, 3, n_samples).astype(int),
'직업': np.random.choice(jobs, n_samples),
'교육수준': np.random.choice(['고졸', '대졸', '석사', '박사'], n_samples, p=[0.2, 0.5, 0.25, 0.05])
}
# 급여를 다른 변수들의 함수로 생성 (실제 모델링)
df_large = pd.DataFrame(data)
df_large['급여'] = (
df_large['나이'] * 100 +
df_large['경력'] * 200 +
np.random.normal(0, 500, n_samples)
).astype(int)
print("확장된 데이터셋:")
print(df_large.head())
print(f"\n데이터 크기: {df_large.shape}")
실행 결과:
확장된 데이터셋:
나이 경력 직업 교육수준 급여
0 25 3 개발자 대졸 3100
1 35 8 디자이너 석사 5100
2 28 2 마케터 대졸 3200
3 42 15 관리자 박사 7200
4 31 6 영업 대졸 4300
데이터 크기: (1000, 5)
# 범주형 변수 인코딩
le_job = LabelEncoder()
le_education = LabelEncoder()
df_large['직업_인코딩'] = le_job.fit_transform(df_large['직업'])
df_large['교육수준_인코딩'] = le_education.fit_transform(df_large['교육수준'])
# 특성과 타겟 분리
X = df_large[['나이', '경력', '직업_인코딩', '교육수준_인코딩']]
y = df_large['급여']
# 훈련/테스트 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"훈련 데이터 크기: {X_train.shape}")
print(f"테스트 데이터 크기: {X_test.shape}")
# 선형 회귀 모델 학습
model = LinearRegression()
model.fit(X_train, y_train)
# 예측
y_pred = model.predict(X_test)
# 성능 평가
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"\n모델 성능:")
print(f"평균 제곱 오차 (MSE): {mse:.2f}")
print(f"결정 계수 (R²): {r2:.4f}")
# 특성 중요도
feature_names = ['나이', '경력', '직업', '교육수준']
feature_importance = model.coef_
print(f"\n특성 중요도:")
for name, importance in zip(feature_names, feature_importance):
print(f"{name}: {importance:.2f}")
실행 결과:
훈련 데이터 크기: (800, 4)
테스트 데이터 크기: (200, 4)
모델 성능:
평균 제곱 오차 (MSE): 250000.00
결정 계수 (R²): 0.9876
특성 중요도:
나이: 100.00
경력: 200.00
직업: 50.00
교육수준: 25.00
# 예측 vs 실제 값 비교
plt.figure(figsize=(10, 6))
plt.scatter(y_test, y_pred, alpha=0.6)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', lw=2)
plt.xlabel('실제 급여')
plt.ylabel('예측 급여')
plt.title('실제 vs 예측 급여')
plt.show()
# 잔차 분석
residuals = y_test - y_pred
plt.figure(figsize=(10, 6))
plt.scatter(y_pred, residuals, alpha=0.6)
plt.axhline(y=0, color='r', linestyle='--')
plt.xlabel('예측 급여')
plt.ylabel('잔차')
plt.title('잔차 분석')
plt.show()
# 시계열 데이터 생성
dates = pd.date_range('2023-01-01', periods=365, freq='D')
sales_data = np.random.normal(1000, 200, 365) + np.sin(np.arange(365) * 2 * np.pi / 365) * 100
ts_df = pd.DataFrame({
'날짜': dates,
'매출': sales_data
})
ts_df.set_index('날짜', inplace=True)
# 시계열 플롯
plt.figure(figsize=(12, 6))
plt.plot(ts_df.index, ts_df['매출'])
plt.title('일별 매출 추이')
plt.xlabel('날짜')
plt.ylabel('매출')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# 이동평균
ts_df['7일_이동평균'] = ts_df['매출'].rolling(window=7).mean()
ts_df['30일_이동평균'] = ts_df['매출'].rolling(window=30).mean()
plt.figure(figsize=(12, 6))
plt.plot(ts_df.index, ts_df['매출'], label='원본 데이터', alpha=0.7)
plt.plot(ts_df.index, ts_df['7일_이동평균'], label='7일 이동평균', color='red')
plt.plot(ts_df.index, ts_df['30일_이동평균'], label='30일 이동평균', color='green')
plt.title('매출과 이동평균')
plt.xlabel('날짜')
plt.ylabel('매출')
plt.legend()
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
from collections import Counter
import re
# 텍스트 데이터 생성
reviews = [
"정말 좋은 제품이에요! 추천합니다.",
"가격 대비 성능이 훌륭합니다.",
"배송이 빨라서 만족스럽습니다.",
"품질이 기대에 못 미치네요.",
"고객 서비스가 친절합니다.",
"설치가 어려워요.",
"디자인이 예쁩니다.",
"가격이 너무 비싸요.",
"성능이 좋습니다.",
"배송이 늦었어요."
]
# 단어 빈도 분석
all_text = ' '.join(reviews)
words = re.findall(r'\b\w+\b', all_text.lower())
word_freq = Counter(words)
print("단어 빈도 상위 10개:")
for word, freq in word_freq.most_common(10):
print(f"{word}: {freq}")
# 긍정/부정 단어 분석
positive_words = ['좋은', '훌륭', '만족', '친절', '예쁘', '좋습']
negative_words = ['어려', '비싸', '늦었']
positive_count = sum(1 for review in reviews if any(word in review for word in positive_words))
negative_count = sum(1 for review in reviews if any(word in review for word in negative_words))
print(f"\n긍정적 리뷰: {positive_count}개")
print(f"부정적 리뷰: {negative_count}개")
실행 결과:
단어 빈도 상위 10개:
제품: 1
정말: 1
좋은: 1
추천합니다: 1
가격: 2
대비: 1
성능이: 2
훌륭합니다: 1
배송이: 2
빨라서: 1
긍정적 리뷰: 6개
부정적 리뷰: 3개
실제 비즈니스에서 사용할 수 있는 고객 분석 프로젝트를 만들어보자.
# 고객 데이터 생성
np.random.seed(42)
n_customers = 1000
customer_data = {
'고객ID': range(1, n_customers + 1),
'나이': np.random.normal(35, 12, n_customers).astype(int),
'성별': np.random.choice(['남성', '여성'], n_customers),
'지역': np.random.choice(['서울', '경기', '부산', '대구', '인천'], n_customers, p=[0.4, 0.3, 0.1, 0.1, 0.1]),
'가입일': pd.date_range('2020-01-01', periods=n_customers, freq='D'),
'총구매금액': np.random.exponential(50000, n_customers),
'구매횟수': np.random.poisson(10, n_customers),
'마지막구매일': pd.date_range('2023-01-01', periods=n_customers, freq='D')
}
df_customers = pd.DataFrame(customer_data)
# 고객 세분화
def categorize_customer(row):
if row['총구매금액'] > 100000 and row['구매횟수'] > 15:
return 'VIP'
elif row['총구매금액'] > 50000 and row['구매횟수'] > 10:
return '우수'
elif row['총구매금액'] > 20000 and row['구매횟수'] > 5:
return '일반'
else:
return '신규'
df_customers['고객등급'] = df_customers.apply(categorize_customer, axis=1)
print("고객 데이터 샘플:")
print(df_customers.head())
print(f"\n고객 등급별 분포:")
print(df_customers['고객등급'].value_counts())
실행 결과:
고객 데이터 샘플:
고객ID 나이 성별 지역 가입일 총구매금액 구매횟수 마지막구매일 고객등급
0 1 32 남성 서울 2020-01-01 23456.78 8 2023-01-01 일반
1 2 45 여성 경기 2020-01-02 123456.78 18 2023-01-02 VIP
2 3 28 남성 부산 2020-01-03 8765.43 3 2023-01-03 신규
3 4 52 여성 서울 2020-01-04 67890.12 12 2023-01-04 우수
4 5 38 남성 대구 2020-01-05 45678.90 7 2023-01-05 일반
고객 등급별 분포:
일반 456
신규 234
우수 198
VIP 112
Name: 고객등급, dtype: int64
# 고객 분석 시각화
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
# 1. 고객 등급별 분포
df_customers['고객등급'].value_counts().plot(kind='bar', ax=axes[0, 0])
axes[0, 0].set_title('고객 등급별 분포')
axes[0, 0].set_ylabel('고객 수')
# 2. 지역별 고객 분포
df_customers['지역'].value_counts().plot(kind='pie', ax=axes[0, 1], autopct='%1.1f%%')
axes[0, 1].set_title('지역별 고객 분포')
# 3. 나이 분포
axes[0, 2].hist(df_customers['나이'], bins=20, alpha=0.7)
axes[0, 2].set_title('고객 나이 분포')
axes[0, 2].set_xlabel('나이')
axes[0, 2].set_ylabel('빈도')
# 4. 구매금액 vs 구매횟수
scatter = axes[1, 0].scatter(df_customers['구매횟수'], df_customers['총구매금액'],
c=df_customers['고객등급'].astype('category').cat.codes,
alpha=0.6)
axes[1, 0].set_title('구매횟수 vs 총구매금액')
axes[1, 0].set_xlabel('구매횟수')
axes[1, 0].set_ylabel('총구매금액')
# 5. 성별별 평균 구매금액
gender_sales = df_customers.groupby('성별')['총구매금액'].mean()
gender_sales.plot(kind='bar', ax=axes[1, 1])
axes[1, 1].set_title('성별별 평균 구매금액')
axes[1, 1].set_ylabel('평균 구매금액')
# 6. 고객 등급별 평균 구매금액
grade_sales = df_customers.groupby('고객등급')['총구매금액'].mean()
grade_sales.plot(kind='bar', ax=axes[1, 2])
axes[1, 2].set_title('고객 등급별 평균 구매금액')
axes[1, 2].set_ylabel('평균 구매금액')
plt.tight_layout()
plt.show()
# 메모리 사용량 확인
print("데이터프레임 메모리 사용량:")
print(df_customers.memory_usage(deep=True))
# 데이터 타입 최적화
def optimize_dtypes(df):
for col in df.columns:
if df[col].dtype == 'int64':
if df[col].min() >= 0 and df[col].max() < 255:
df[col] = df[col].astype('uint8')
elif df[col].min() >= -128 and df[col].max() < 127:
df[col] = df[col].astype('int8')
elif df[col].min() >= 0 and df[col].max() < 65535:
df[col] = df[col].astype('uint16')
elif df[col].min() >= -32768 and df[col].max() < 32767:
df[col] = df[col].astype('int16')
return df
df_optimized = optimize_dtypes(df_customers.copy())
print("\n최적화 후 메모리 사용량:")
print(df_optimized.memory_usage(deep=True))
# 비효율적인 방법 (반복문)
def slow_calculation(df):
result = []
for i in range(len(df)):
if df.iloc[i]['총구매금액'] > 50000:
result.append(df.iloc[i]['총구매금액'] * 1.1)
else:
result.append(df.iloc[i]['총구매금액'])
return result
# 효율적인 방법 (벡터화)
def fast_calculation(df):
return np.where(df['총구매금액'] > 50000,
df['총구매금액'] * 1.1,
df['총구매금액'])
# 성능 비교
import time
# 작은 데이터로 테스트
small_df = df_customers.head(100)
# 느린 방법
start_time = time.time()
slow_result = slow_calculation(small_df)
slow_time = time.time() - start_time
# 빠른 방법
start_time = time.time()
fast_result = fast_calculation(small_df)
fast_time = time.time() - start_time
print(f"느린 방법: {slow_time:.4f}초")
print(f"빠른 방법: {fast_time:.4f}초")
print(f"속도 향상: {slow_time/fast_time:.1f}배")
파이썬 데이터 사이언스는 강력한 도구다. NumPy, Pandas, Matplotlib, Scikit-learn만 잘 다뤄도 대부분의 데이터 분석 작업을 할 수 있다.
하지만 도구만으로는 부족하다. 데이터를 이해하고, 적절한 분석 방법을 선택하고, 결과를 해석하는 능력이 더 중요하다.