0. 들어가며
이번 시간에는 Matplotlib를 활용하여 다양한 형태의 그래프를 그리는 방법을 학습하였다.
선 그래프, 막대 그래프, 파이 차트, 산점도, 히스토그램 등 여러 종류의 그래프를 배웠는데 단순한 예제 데이터보다 실제 데이터를 활용하면 더 재미있게 학습할 수 있을 것 같았다.
그래서 공개된 포켓몬 데이터셋을 활용하여 Matplotlib의 다양한 그래프를 직접 그려보며 시각화 방법을 정리해보았다.
1. Matplotlib란?
Matplotlib는 파이썬(Python)에서 데이터를 시각화할 때 가장 널리 사용되는 데이터 시각화 라이브러리이다. NumPy 배열이나 Pandas의 DataFrame과 함께 자주 사용되며, 텍스트 형태의 수많은 데이터를 직관적인 그래프로 표현하여 데이터의 특징과 분포, 추이를 한눈에 파악할 수 있도록 도와준다.
import matplotlib.pyplot as plt
2. 오늘 학습한 주요 그래프
- 선 그래프 (plt.plot())
- 역할: 데이터의 변화 추이를 확인하거나 시간의 흐름, 순서가 있는 데이터를 표현할 때 적합하다.
- 막대 그래프 (plt.bar())
- 역할: 집단 간의 크기나 평균 능력치 등을 비교할 때 유용하다.
- 파이 차트 (plt.pie())
- 역할: 전체 데이터에서 각 항목이 차지하는 비율을 직관적으로 확인할 때 적합하다.
- 히스토그램 (plt.hist())
- 역할: 연속형 데이터가 어떤 구간(Bin)에 집중되어 있는지 분포를 확인하기에 좋다.
- 산점도 (plt.scatter())
- 역할: 두 변수(X, Y) 사이의 상관관계나 분포적 특징을 시각적으로 확인할 수 있다.
3. 실습 프로젝트: 포켓몬 데이터에 적용해보기
단순한 예제 리스트나 임의의 배열 데이터를 그리는 것에서 나아가, 실제 공개된 포켓몬 데이터셋을 활용해 위에서 정리한 5가지 그래프를 직접 시각화해 보며 라이브러리 활용법을 탄탄히 다져보았다.
⚡데이터 준비
시각화에 앞서 Pandas를 사용해 깃허브에 공개된 포켓몬 데이터셋을 불러오고, 각 포켓몬의 기술 스탯 총합인 Total 컬럼을 생성해 주었다.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 데이터 불러오기
url = "https://raw.githubusercontent.com/KeithGalli/pandas/master/pokemon_data.csv"
df = pd.read_csv(url)
# 종합 능력치(Total) 컬럼 계산
df["Total"] = (df["HP"] + df["Attack"] + df["Defense"] +
df["Sp. Atk"] + df["Sp. Def"] + df["Speed"])
# Name Type 1 Type 2 HP Attack Defense Sp. Atk \
0 1 Bulbasaur Grass Poison 45 49 49 65
1 2 Ivysaur Grass Poison 60 62 63 80
2 3 Venusaur Grass Poison 80 82 83 100
3 3 VenusaurMega Venusaur Grass Poison 80 100 123 122
4 4 Charmander Fire NaN 39 52 43 60
Sp. Def Speed Generation Legendary Total
0 65 45 1 False 318
1 80 60 1 False 405
2 100 80 1 False 525
3 120 80 1 False 625
4 50 65 1 False 309
<class 'pandas.DataFrame'>
RangeIndex: 800 entries, 0 to 799
Data columns (total 13 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 # 800 non-null int64
1 Name 800 non-null str
2 Type 1 800 non-null str
3 Type 2 414 non-null str
4 HP 800 non-null int64
5 Attack 800 non-null int64
6 Defense 800 non-null int64
7 Sp. Atk 800 non-null int64
8 Sp. Def 800 non-null int64
9 Speed 800 non-null int64
10 Generation 800 non-null int64
11 Legendary 800 non-null bool
12 Total 800 non-null int64
dtypes: bool(1), int64(9), str(3)
memory usage: 75.9 KB
None
Index(['#', 'Name', 'Type 1', 'Type 2', 'HP', 'Attack', 'Defense', 'Sp. Atk',
'Sp. Def', 'Speed', 'Generation', 'Legendary', 'Total'],
dtype='str')
데이터는 총 800개의 행(포켓몬)과 이름, 타입, 체력(HP), 공격력, 방어력, 세대(Generation), 전설 여부(Legendary) 등 13개의 컬럼으로 구성되어 있다.
⚡파이 차트 : 전설 포켓몬 비율
전체 포켓몬 중 일반 포켓몬과 전설 포켓몬이 각각 얼마의 비율을 차지하는지 알아보기 위해 비율 시각화에 적합한 파이 차트(plt.pie())를 활용했다.
# 전설 포켓몬 여부 카운트
legendary_counts = df["Legendary"].value_counts()
plt.pie(
legendary_counts,
labels=["Normal", "Legendary"],
autopct='%1.1f%%',
startangle=90
)
plt.title('Normal vs Legendary Pokemon Ratio')
plt.show()

분석 결과 및 배운 점: 시각화 결과 일반 포켓몬은 약 91.9%(735마리), 전설 포켓몬은 8.1%(65마리)로 나타났다. 전체 데이터 속에서 특정 범주가 가지는 점유율을 한눈에 보기에 파이 차트가 매우 직관적임을 알 수 있었다.
⚡막대 그래프 : 전설 vs 일반 평균 능력치 비교
두 집단(일반 포켓몬 그룹과 전설 포켓몬 그룹)의 스탯(HP, Attack, Defense 등)별 평균 능력치를 서로 명확히 비교하고자 plt.bar()를 사용했다.
# 일반 및 전설 그룹 분리
legendary = df[df["Legendary"] == True]
normal = df[df["Legendary"] == False]
stats = ["HP", "Attack", "Defense", "Sp. Atk", "Sp. Def", "Speed"]
legendary_means = np.mean(legendary[stats], axis=0)
normal_means = np.mean(normal[stats], axis=0)
x = np.arange(len(stats))
width = 0.35
plt.bar(x - width/2, normal_means, width, label="Normal")
plt.bar(x + width/2, legendary_means, width, label="Legendary")
plt.xticks(x, stats)
plt.ylabel('Average Stat')
plt.title('Average Stats: Normal vs Legendary')
plt.legend()
plt.show()

분석 결과 및 배운 점: 모든 세부 능력치 스탯에서 전설 포켓몬이 일반 포켓몬에 비해 압도적으로 높은 평균치를 가지고 있었다.
⚡막대 그래프 : 포켓몬 타입별 평균 능력치(Total) 비교
포켓몬은 불꽃, 물, 풀 등 다양한 'Type 1' 속성을 가지고 있다. 무려 18가지나 되는 복잡한 타입별로 평균 종합 능력치(Total)가 어떻게 다른지 한눈에 비교하기 위해 막대 그래프(plt.bar())를 활용해 보았다.
# 타입(Type 1)별 종합 능력치 평균 계산 및 정렬
type_total_mean = df.groupby("Type 1")["Total"].mean().sort_values()
# 막대 그래프 그리기
plt.figure(figsize=(10, 6))
plt.bar(type_total_mean.index, type_total_mean.values, color='skyblue', edgecolor='black')
plt.xlabel('Pokemon Type 1')
plt.ylabel('Average Total Stat')
plt.title('Average Total Stat by Pokemon Type')
# x축 라벨이 겹치지 않도록 90도 회전
plt.xticks(rotation=90)
plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.show()

분석 결과 및 배운 점: 시각화 결과, 평균 Total 스탯이 가장 높은 강력한 타입은 드래곤(Dragon)과 강철(Steel) 타입으로 나타났다. 반면 가장 평균 능력치가 낮은 타입은 벌레(Bug) 타입이었다. 비교해야 할 항목(범주)이 18개로 매우 많을 때는 일반적인 막대 그래프를 그리면 x축 글자가 서로 겹쳐서 알아볼 수 없게 된다. 이를plt.xticks(rotation=90) 옵션을 통해 텍스트를 세로로 회전시키고, 수치 크기 순으로 정렬(sort_values())해 주면 항목이 많아도 가독성을 해치지 않고 깔끔하게 순위를 비교할 수 있었다.
⚡히스토그램 : Total 분포 확인
포켓몬들의 종합 능력치(Total) 스탯이 대략 어느 구간에 촘촘히 모여있고 어떻게 흩어져 있는지 데이터의 빈도와 밀도를 보기 위해 히스토그램(plt.hist())을 사용했다.
plt.hist(normal["Total"], bins=20, alpha=0.7, label="Normal")
plt.hist(legendary["Total"], bins=20, alpha=0.7, label="Legendary")
plt.xlabel("Total Stat")
plt.ylabel("Count")
plt.title("Total Stat Distribution")
plt.legend()
plt.show()

분석 결과 및 배운 점: 일반 포켓몬의 Total 평균은 약 417.2점, 중앙값은 425점 부근에 넓게 분포하는 반면, 전설 포켓몬은 평균 약 637.4점, 중앙값 600점이라는 높은 구간에 조밀하게 뭉쳐 분포하고 있음을 알 수 있다.
⚡산점도 : 공격력 vs 스피드 관계
포켓몬의 '공격력(Attack)'이 높을수록 '스피드(Speed)'도 함께 빠를지, 두 변수 사이에 어떤 상관관계가 존재하는지 좌표평면 위에 점으로 뿌려 확인하기 위해 산점도(plt.scatter())를 그렸다.
plt.scatter(normal["Attack"], normal["Speed"], alpha=0.5, label="Normal", color='blue')
plt.scatter(legendary["Attack"], legendary["Speed"], alpha=0.8, label="Legendary", color='red')
plt.xlabel("Attack")
plt.ylabel("Speed")
plt.title("Attack vs Speed")
plt.legend()
plt.show()

분석 결과 및 배운 점: 점들이 흩어진 양상을 보니 공격력과 스피드가 완벽하게 비례하지는 않지만, 전설 포켓몬(빨간 점)들은 대체로 우상향 구간(높은 공격력과 높은 스피드)에 치우쳐 자리 잡고 있음을 확인할 수 있었다.
⚡선 그래프 : 세대별 평균 능력치 변화
시간의 흐름이나 순서(1세대 ~ 6세대)에 따른 포켓몬들의 평균 종합 스탯(Total) 추이가 어떻게 변화하는지 연속적인 흐름을 추적하기 위해 선 그래프(plt.plot())를 활용했다.
# 세대(Generation)별 종합 능력치 평균 계산
generation_total = df.groupby("Generation")["Total"].mean()
plt.plot(
generation_total.index,
generation_total.values,
marker='o'
)
plt.grid(True)
plt.title("Average Total Stat by Generation")
plt.xlabel("Generation")
plt.ylabel("Average Total")
plt.show()

분석 결과 및 배운 점: 세대가 거듭될수록 포켓몬의 평균 능력치가 계속 상승하기만 하는 것이 아니라, 특정 세대에서 부침을 겪으며 변화하는 '추세'를 꺾은선과 마커(marker='o')를 통해 매끄럽게 파악할 수 있었다.
💭메무아
이번 실습에서는 단순히 Matplotlib 함수 사용법을 익히는 것에서 끝나지 않고 실제 데이터셋에 적용해보았다.
같은 데이터라도 어떤 그래프를 선택하느냐에 따라 전달되는 정보가 달라졌으며, 데이터의 특성에 맞는 시각화 방법을 선택하는 것이 중요하다는 점을 느낄 수 있었다.
—————————————————————————————————————————————————————
본 후기는 [한글과컴퓨터x한국생산성본부x스나이퍼팩토리]
한컴 AI 아카데미 (B-log) 리뷰로 작성 되었습니다.
