Post

데이터 분석 기초 - 자동차 결함 리콜 데이터 분석 실습

데이터 분석 기초 - 자동차 결함 리콜 데이터 분석 실습

데이터 분석 기초 - 자동차 결함 리콜 데이터 분석 실습

개요

데이터 분석의 기본 과정을 자동차 결함 리콜 데이터를 통해 실습합니다:

  • 데이터 읽기: DataFrame 구조 확인
  • 데이터 정제: 결측치, 중복값 확인 및 처리
  • 데이터 시각화: 데이터 특성 파악을 위한 시각화
  • 실무 적용: 실제 데이터 분석 프로젝트 경험

1. 데이터 읽기

기본 데이터 로딩

1
2
3
4
5
6
7
8
9
10
11
12
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# 데이터 읽기 (한글 인코딩 주의)
df = pd.read_csv('데이터경로', encoding='euc_kr')

# 데이터 기본 정보 확인
df.head()      # 상위 5행 확인
df.tail()      # 하위 5행 확인
df.info()      # 데이터 타입, 결측치 정보
df.describe()  # 기초 통계 정보 (수치형 데이터)

데이터 구조 파악

1
2
3
4
5
6
7
8
9
10
11
12
# 데이터 형태 확인
print(f"데이터 크기: {df.shape}")
print(f"컬럼 수: {df.shape[1]}")
print(f"행 수: {df.shape[0]}")

# 컬럼 정보
print("컬럼 목록:")
print(df.columns.tolist())

# 데이터 타입 확인
print("\n데이터 타입:")
print(df.dtypes)

2. 데이터 정제

2-1. 결측치 확인 및 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 각 열별 결측치 개수 확인
missing_data = df.isnull().sum()
print("결측치 현황:")
print(missing_data[missing_data > 0])

# 결측치 비율 확인
missing_ratio = (df.isnull().sum() / len(df)) * 100
print("\n결측치 비율:")
print(missing_ratio[missing_ratio > 0])

# 결측치 시각화
import seaborn as sns
plt.figure(figsize=(10, 6))
sns.heatmap(df.isnull(), cbar=True, yticklabels=False)
plt.title('결측치 분포')
plt.show()

2-2. 중복값 확인 및 처리

1
2
3
4
5
6
7
8
9
10
11
12
# 중복값 확인
duplicated_rows = df[df.duplicated(keep=False)]
print(f"중복된 행 수: {len(duplicated_rows)}")

# 중복값 상세 확인
if len(duplicated_rows) > 0:
    print("중복된 행들:")
    print(duplicated_rows)

# 중복값 제거
df_cleaned = df.drop_duplicates()
print(f"중복 제거 후 데이터 크기: {df_cleaned.shape}")

2-3. 데이터 타입 변환 및 새로운 컬럼 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 날짜 타입 변환
df['생산기간(부터)'] = pd.to_datetime(df['생산기간(부터)'])
df['생산기간(까지)'] = pd.to_datetime(df['생산기간(까지)'])
df['리콜개시일'] = pd.to_datetime(df['리콜개시일'])

# 새로운 컬럼 생성 (년, 월, 일 추출)
df['생산_년'] = df['생산기간(부터)'].dt.year
df['생산_월'] = df['생산기간(부터)'].dt.month
df['생산_일'] = df['생산기간(부터)'].dt.day

df['리콜_년'] = df['리콜개시일'].dt.year
df['리콜_월'] = df['리콜개시일'].dt.month
df['리콜_일'] = df['리콜개시일'].dt.day

# 불필요한 컬럼 삭제
columns_to_drop = ['생산기간(부터)', '생산기간(까지)', '리콜개시일']
df = df.drop(columns_to_drop, axis=1)

2-4. 데이터 값 분석

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 특정 컬럼의 고유값 확인
print("리콜 연도별 고유값:")
print(df['리콜_년'].unique())

print(f"\n리콜 연도 고유값 개수: {df['리콜_년'].nunique()}")

# 값 분포 확인
print("\n리콜 연도별 빈도:")
print(df['리콜_년'].value_counts().sort_index())

# 제작자별 리콜 건수 집계
manufacturer_recalls = df.groupby('제작자').size().sort_values(ascending=False)
print("\n제작자별 리콜 건수:")
print(manufacturer_recalls.head(10))

3. 데이터 시각화

3-1. 막대 그래프 (Bar Chart)

1
2
3
4
5
6
7
8
9
10
11
12
# 제작자별 리콜 건수 막대 그래프
top_manufacturers = df.groupby('제작자').size().sort_values(ascending=False).head(10)

plt.figure(figsize=(12, 6))
plt.bar(x=top_manufacturers.index, height=top_manufacturers.values)
plt.xticks(rotation=45, ha='right')
plt.title('제작자별 리콜 건수 현황 (Top 10)', fontsize=14, fontweight='bold')
plt.xlabel('제작자', fontsize=12)
plt.ylabel('리콜 건수', fontsize=12)
plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()

3-2. 원형 그래프 (Pie Chart)

1
2
3
4
5
6
7
8
9
10
11
# 제작자별 리콜 건수 원형 그래프 (Top 10)
top_10_manufacturers = df.groupby('제작자').size().sort_values(ascending=False).head(10)

plt.figure(figsize=(10, 8))
plt.pie(top_10_manufacturers.values, 
        labels=top_10_manufacturers.index,
        autopct='%.1f%%',
        startangle=90)
plt.title('제작자별 리콜 건수 비율 (Top 10)', fontsize=14, fontweight='bold')
plt.axis('equal')
plt.show()

3-3. 히스토그램

1
2
3
4
5
6
7
8
# 리콜 연도별 분포 히스토그램
plt.figure(figsize=(12, 6))
plt.hist(df['리콜_년'], bins=20, edgecolor='black', alpha=0.7)
plt.title('리콜 연도별 분포', fontsize=14, fontweight='bold')
plt.xlabel('리콜 연도', fontsize=12)
plt.ylabel('빈도', fontsize=12)
plt.grid(axis='y', alpha=0.3)
plt.show()

3-4. 박스 플롯

1
2
3
4
5
6
7
8
9
10
# 제작자별 리콜 건수 박스 플롯
plt.figure(figsize=(15, 8))
top_5_manufacturers = df.groupby('제작자').size().sort_values(ascending=False).head(5).index
df_top5 = df[df['제작자'].isin(top_5_manufacturers)]

sns.boxplot(data=df_top5, x='제작자', y='리콜_년')
plt.title('주요 제작자별 리콜 연도 분포', fontsize=14, fontweight='bold')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

4. 워드 클라우드

4-1. 기본 워드 클라우드

1
2
3
4
5
6
from wordcloud import WordCloud, STOPWORDS
import matplotlib.pyplot as plt

# 기본 stopwords 확인
print("기본 stopwords:")
print(set(STOPWORDS))

4-2. 의미 있는 중복을 포함한 워드 클라우드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 리콜 사유 텍스트 결합
text = ' '.join(df['리콜사유'].values)

# 한국어 stopwords 설정
korean_stopwords = set([
    "동안", "인하여", "있는", "경우", "있습니다", "가능성이", "않을", "차량의", 
    "", "", "", "", "인해", "", "", "", "또는", "있음", "", 
    "", "있으며", "발생할", "이로", "오류로", "해당", "때문에", "위해", 
    "통해", "관련", "대한", "하여", "되는", "", "", "있는"
])

# 워드 클라우드 생성
wordcloud = WordCloud(
    max_font_size=200,
    stopwords=korean_stopwords,
    font_path='./NanumGothic.ttf',  # 한글 폰트 경로
    background_color='white',
    width=800,
    height=600,
    colormap='viridis'
).generate(text)

# 워드 클라우드 시각화
plt.figure(figsize=(12, 8))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.title('리콜 사유 워드 클라우드', fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()

4-3. 중복 제거 후 워드 클라우드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 중복 제거된 리콜 사유 텍스트
unique_text = ' '.join(df['리콜사유'].drop_duplicates().values)

# 워드 클라우드 생성 (중복 제거 버전)
wordcloud_unique = WordCloud(
    max_font_size=200,
    stopwords=korean_stopwords,
    font_path='./NanumGothic.ttf',
    background_color='white',
    width=800,
    height=600,
    colormap='plasma'
).generate(unique_text)

# 워드 클라우드 시각화
plt.figure(figsize=(12, 8))
plt.imshow(wordcloud_unique, interpolation='bilinear')
plt.axis('off')
plt.title('리콜 사유 워드 클라우드 (중복 제거)', fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()

5. 고급 데이터 분석

5-1. 시계열 분석

1
2
3
4
5
6
7
8
9
10
11
12
13
# 월별 리콜 건수 추이
monthly_recalls = df.groupby(['리콜_년', '리콜_월']).size().reset_index(name='recall_count')
monthly_recalls['date'] = pd.to_datetime(monthly_recalls[['리콜_년', '리콜_월']].assign(day=1))

plt.figure(figsize=(15, 6))
plt.plot(monthly_recalls['date'], monthly_recalls['recall_count'], marker='o')
plt.title('월별 리콜 건수 추이', fontsize=14, fontweight='bold')
plt.xlabel('날짜', fontsize=12)
plt.ylabel('리콜 건수', fontsize=12)
plt.xticks(rotation=45)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

5-2. 상관관계 분석

1
2
3
4
5
6
7
8
9
# 수치형 데이터 간 상관관계
numeric_columns = ['생산_년', '생산_월', '리콜_년', '리콜_월']
correlation_matrix = df[numeric_columns].corr()

plt.figure(figsize=(8, 6))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0)
plt.title('수치형 변수 간 상관관계', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()

5-3. 제작자별 상세 분석

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 제작자별 리콜 연도 분포
top_manufacturers = df.groupby('제작자').size().sort_values(ascending=False).head(5).index

fig, axes = plt.subplots(2, 3, figsize=(18, 12))
axes = axes.ravel()

for i, manufacturer in enumerate(top_manufacturers):
    if i < 5:
        manufacturer_data = df[df['제작자'] == manufacturer]
        axes[i].hist(manufacturer_data['리콜_년'], bins=15, alpha=0.7, edgecolor='black')
        axes[i].set_title(f'{manufacturer} 리콜 연도 분포', fontweight='bold')
        axes[i].set_xlabel('리콜 연도')
        axes[i].set_ylabel('빈도')
        axes[i].grid(alpha=0.3)

# 빈 subplot 제거
if len(top_manufacturers) < 6:
    axes[5].remove()

plt.suptitle('주요 제작자별 리콜 연도 분포', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()

6. 데이터 분석 결과 요약

6-1. 주요 통계 요약

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 전체 데이터 요약
print("=== 데이터 분석 결과 요약 ===")
print(f"총 리콜 건수: {len(df):,}")
print(f"분석 기간: {df['리콜_년'].min()}년 ~ {df['리콜_년'].max()}")
print(f"제작자 수: {df['제작자'].nunique()}")

print(f"\n리콜이 가장 많은 제작자 Top 5:")
top_5 = df.groupby('제작자').size().sort_values(ascending=False).head(5)
for i, (manufacturer, count) in enumerate(top_5.items(), 1):
    print(f"{i}. {manufacturer}: {count:,}")

print(f"\n연도별 리콜 건수:")
yearly_recalls = df.groupby('리콜_년').size().sort_index()
for year, count in yearly_recalls.items():
    print(f"{year}년: {count:,}")

6-2. 인사이트 도출

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 리콜 패턴 분석
print("\n=== 리콜 패턴 분석 ===")

# 가장 많은 리콜이 발생한 연도
max_recall_year = yearly_recalls.idxmax()
max_recall_count = yearly_recalls.max()
print(f"가장 많은 리콜이 발생한 연도: {max_recall_year}년 ({max_recall_count:,}건)")

# 제작자별 리콜 비율
total_recalls = len(df)
top_manufacturer = top_5.index[0]
top_manufacturer_count = top_5.iloc[0]
top_manufacturer_ratio = (top_manufacturer_count / total_recalls) * 100
print(f"리콜이 가장 많은 제작자: {top_manufacturer} ({top_manufacturer_ratio:.1f}%)")

# 리콜 증가/감소 추이
recent_years = yearly_recalls.tail(3)
if len(recent_years) >= 2:
    trend = "증가" if recent_years.iloc[-1] > recent_years.iloc[-2] else "감소"
    print(f"최근 리콜 추이: {trend}")

7. 실무 적용 팁

7-1. 데이터 품질 관리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 데이터 품질 체크리스트
def data_quality_check(df):
    """데이터 품질 체크 함수"""
    print("=== 데이터 품질 체크 ===")
    
    # 1. 결측치 체크
    missing_data = df.isnull().sum()
    missing_ratio = (missing_data / len(df)) * 100
    
    print("1. 결측치 현황:")
    for col, ratio in missing_ratio.items():
        if ratio > 0:
            print(f"   {col}: {missing_data[col]}개 ({ratio:.1f}%)")
    
    # 2. 중복값 체크
    duplicates = df.duplicated().sum()
    print(f"\n2. 중복값: {duplicates}")
    
    # 3. 데이터 타입 체크
    print("\n3. 데이터 타입:")
    for col, dtype in df.dtypes.items():
        print(f"   {col}: {dtype}")
    
    # 4. 이상값 체크 (수치형 데이터)
    numeric_cols = df.select_dtypes(include=[np.number]).columns
    print("\n4. 이상값 체크 (수치형 데이터):")
    for col in numeric_cols:
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1
        outliers = df[(df[col] < Q1 - 1.5*IQR) | (df[col] > Q3 + 1.5*IQR)]
        print(f"   {col}: {len(outliers)}개 이상값")

# 데이터 품질 체크 실행
data_quality_check(df)

7-2. 시각화 모범 사례

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 시각화 설정 함수
def setup_plot_style():
    """시각화 스타일 설정"""
    plt.style.use('seaborn-v0_8')
    plt.rcParams['figure.figsize'] = (12, 8)
    plt.rcParams['font.size'] = 12
    plt.rcParams['axes.titlesize'] = 14
    plt.rcParams['axes.labelsize'] = 12
    plt.rcParams['xtick.labelsize'] = 10
    plt.rcParams['ytick.labelsize'] = 10
    plt.rcParams['legend.fontsize'] = 10

# 스타일 적용
setup_plot_style()

마무리

데이터 분석의 기본 과정을 자동차 결함 리콜 데이터를 통해 실습했습니다:

핵심 학습 내용

  • 데이터 읽기: 다양한 형식의 데이터 로딩과 기본 정보 확인
  • 데이터 정제: 결측치, 중복값 처리 및 데이터 타입 변환
  • 데이터 시각화: 막대 그래프, 원형 그래프, 히스토그램, 워드 클라우드
  • 고급 분석: 시계열 분석, 상관관계 분석, 패턴 분석

실무 적용

  • 데이터 품질 관리: 체계적인 데이터 검증 과정
  • 시각화 모범 사례: 효과적인 데이터 시각화 방법
  • 인사이트 도출: 데이터에서 의미 있는 패턴 발견

이러한 기본 과정을 통해 실제 데이터 분석 프로젝트에서 활용할 수 있는 실무 역량을 기를 수 있습니다.

This post is licensed under CC BY 4.0 by the author.