import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import platform
# 운영체제별 한글 폰트 설정
if platform.system() == "Darwin":
plt.rcParams["font.family"] = "Apple SD Gothic Neo"
elif platform.system() == "Windows":
plt.rcParams["font.family"] = "Malgun Gothic"
else:
plt.rcParams["font.family"] = "NanumGothic"
plt.rcParams["axes.unicode_minus"] = False3 실습: pandas 시작하기
Gapminder 데이터셋은 전 세계 국가들의 인구, 기대수명, 1인당 GDP 등 사회경제적 지표를 시계열로 제공하는 유명한 데이터셋입니다. ‘gapminder-extended.tsv’ 파일을 사용하여 데이터 읽기부터 시각화까지 전 과정을 단계별로 실습해보겠습니다.
3.1 파일 읽기
데이터 분석의 첫 단계는 데이터를 읽어오는 것입니다. Gapminder 데이터는 TSV(Tab-Separated Values) 형식으로 저장되어 있어, 각 열이 탭 문자로 구분되어 있습니다. pandas의 read_csv() 함수는 기본적으로 쉼표로 구분된 CSV 파일을 읽지만, sep 매개변수를 통해 구분자를 지정할 수 있어 TSV 파일도 쉽게 읽을 수 있습니다.
데이터 분석에 필요한 라이브러리들을 먼저 임포트합니다. pandas는 데이터 조작과 분석을, NumPy는 수치 연산을, matplotlib과 seaborn은 시각화를 담당합니다. platform 모듈은 운영체제를 확인하여 한글 폰트 설정을 자동으로 조정하는 데 사용됩니다.
read_csv() 함수의 sep="\t" 매개변수는 탭 문자를 구분자로 사용하겠다는 의미입니다. 이렇게 읽어온 데이터는 DataFrame 객체로 저장되며, 이후 모든 분석 작업의 기반이 됩니다.
3.2 데이터 확인
파일을 읽은 후에는 데이터의 구조와 내용을 파악하는 것이 중요합니다. 데이터의 크기, 컬럼의 종류와 타입, 결측치의 존재 여부 등을 확인함으로써 이후 분석 방향을 설정하고 잠재적인 문제점을 조기에 발견할 수 있습니다.
3.2.1 기본 정보 확인
info() 메서드는 데이터프레임의 전체적인 구조를 한눈에 파악할 수 있게 해주는 핵심 메서드입니다. 이 메서드는 데이터프레임의 행과 열의 개수, 각 컬럼의 이름과 데이터 타입, 메모리 사용량, 그리고 각 컬럼의 결측치 개수를 요약하여 보여줍니다. 특히 데이터 타입을 확인하는 것은 중요합니다. 예를 들어, 숫자로 보이는 데이터가 문자열 타입으로 저장되어 있다면 수치 연산 전에 타입 변환이 필요할 수 있습니다.
info() 메서드의 출력 결과를 보면 각 컬럼의 Non-Null Count가 표시되는데, 이는 해당 컬럼에 결측치가 없는 행의 개수를 의미합니다. 전체 행 수와 Non-Null Count를 비교하면 각 컬럼의 결측치 개수를 쉽게 파악할 수 있습니다.
<class 'pandas.DataFrame'>
RangeIndex: 5879 entries, 0 to 5878
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 country 5879 non-null str
1 continent 5879 non-null str
2 year 5879 non-null int64
3 lifeExp 5879 non-null float64
4 pop 5879 non-null float64
5 gdpPercap 5879 non-null float64
dtypes: float64(3), int64(1), str(2)
memory usage: 357.4 KB
3.2.2 데이터 확인
대용량 데이터를 다룰 때는 전체 데이터를 출력하는 것보다 일부만 확인하는 것이 효율적입니다. pandas의 head()와 tail() 메서드는 데이터프레임의 처음 또는 마지막 몇 행을 빠르게 확인할 수 있게 해줍니다. 이 메서드들은 데이터의 구조, 컬럼명, 실제 값의 예시를 확인하거나 데이터 전처리 과정에서 중간 결과물을 신속하게 점검할 때 널리 사용됩니다.
head(n) 메서드는 데이터프레임의 처음 \(n\)개 행을 반환합니다. 기본값은 5개이며, 데이터가 정렬되어 있다면 상위 데이터를 확인할 수 있습니다. 이 메서드를 사용하면 데이터의 구조, 컬럼명, 값의 범위와 패턴을 빠르게 파악할 수 있습니다. 특히 데이터가 예상한 형식으로 잘 읽혔는지, 컬럼명이 올바른지 확인하는 데 유용합니다.
| country | continent | ... | pop | gdpPercap | |
|---|---|---|---|---|---|
| 0 | Afghanistan | Asia | ... | 8425333.0 | 779.445314 |
| 1 | Afghanistan | Asia | ... | 9240934.0 | 820.853030 |
| 2 | Afghanistan | Asia | ... | 10267083.0 | 853.100710 |
3 rows × 6 columns
tail(n) 메서드는 데이터프레임의 마지막 \(n\)개 행을 반환합니다. 기본값은 5개이며, 데이터의 끝부분을 확인할 때 사용합니다. 데이터가 시간순으로 정렬되어 있다면 최신 데이터를 확인할 수 있고, 데이터의 일관성을 검증하는 데 도움이 됩니다. 예를 들어, 데이터가 연도순으로 정렬되어 있다면 tail()을 통해 최신 연도의 데이터를 확인할 수 있습니다.
| country | continent | ... | pop | gdpPercap | |
|---|---|---|---|---|---|
| 5869 | Zimbabwe | Africa | ... | 14013808.0 | 5031.687444 |
| 5870 | Zimbabwe | Africa | ... | 14207359.0 | 5081.112404 |
| 5871 | Zimbabwe | Africa | ... | 14399013.0 | 5102.714323 |
| ... | ... | ... | ... | ... | ... |
| 5876 | Zimbabwe | Africa | ... | 15526888.0 | 4527.719881 |
| 5877 | Zimbabwe | Africa | ... | 15797210.0 | 4827.088694 |
| 5878 | Zimbabwe | Africa | ... | 16069056.0 | 5036.761361 |
10 rows × 6 columns
데이터를 특정 기준으로 정렬한 후 head()를 사용하면 상위 데이터를 확인할 수 있습니다. 예를 들어, 기대수명으로 정렬한 후 head(10)을 사용하면 기대수명이 높은 10개 국가를 확인할 수 있습니다.
3.2.3 기초 통계량 확인
수치형 변수에 대한 기본적인 통계량을 한눈에 확인하는 것은 데이터의 분포와 특성을 이해하는 데 필수적입니다. describe() 메서드는 수치형 컬럼에 대해 평균, 표준편차, 최솟값, 최댓값, 그리고 25%, 50%(중앙값), 75% 사분위수를 자동으로 계산하여 보여줍니다.
이 메서드의 출력 결과를 통해 데이터의 중심 경향성(평균, 중앙값), 변동성(표준편차), 그리고 분포의 범위(최솟값, 최댓값)를 빠르게 파악할 수 있습니다. 특히 표준편차가 크면 데이터의 변동성이 크다는 의미이며, 평균과 중앙값의 차이가 크면 데이터가 한쪽으로 치우쳐 있다는 것을 의미합니다. 또한 사분위수를 통해 데이터의 대부분이 어떤 범위에 있는지 확인할 수 있어 이상치를 발견하는 데도 도움이 됩니다.
describe() 메서드는 기본적으로 수치형 컬럼만 처리하며, 문자열이나 범주형 데이터는 자동으로 제외됩니다. 이는 수치 연산이 가능한 데이터에 대해서만 통계량을 계산하기 때문입니다.
| year | lifeExp | pop | gdpPercap | |
|---|---|---|---|---|
| count | 5879.000000 | 5879.000000 | 5.879000e+03 | 5879.000000 |
| mean | 1998.395475 | 65.637034 | 4.243653e+07 | 15997.267161 |
| std | 17.211361 | 11.824726 | 1.453911e+08 | 19079.561453 |
| ... | ... | ... | ... | ... |
| 50% | 2002.000000 | 68.629000 | 9.826397e+06 | 7911.010258 |
| 75% | 2011.000000 | 75.192073 | 2.854028e+07 | 20980.662874 |
| max | 2022.000000 | 85.532683 | 1.425423e+09 | 133571.962410 |
8 rows × 4 columns
3.2.4 타입 확인
각 컬럼의 데이터 타입을 확인하는 것은 데이터 분석의 기초가 됩니다. 데이터 타입이 올바르지 않으면 연산 오류가 발생하거나 예상과 다른 결과가 나올 수 있습니다. 예를 들어, 날짜 데이터가 문자열로 저장되어 있다면 날짜 연산을 수행할 수 없고, 숫자 데이터가 문자열로 저장되어 있다면 수치 연산이 불가능합니다.
dtypes 속성은 데이터프레임의 각 컬럼의 데이터 타입을 Series 형태로 반환합니다. 이 속성을 확인하면 각 컬럼이 정수형(int64), 실수형(float64), 문자열(object 또는 str), 날짜형(datetime64) 등 어떤 타입으로 저장되어 있는지 알 수 있습니다. 데이터 타입을 확인한 후 필요하다면 astype() 메서드를 사용하여 타입을 변환할 수 있습니다.
3.2.5 결측치 확인
결측치(missing value)는 데이터 분석에서 매우 중요한 이슈입니다. 결측치가 있는 데이터로 분석을 수행하면 결과가 왜곡될 수 있으므로, 분석 전에 결측치의 존재 여부와 개수를 확인하는 것이 필수적입니다.
isnull() 메서드는 데이터프레임의 각 셀에 대해 결측치 여부를 Boolean 값(True/False)으로 반환합니다. True는 해당 위치에 결측치가 있다는 의미이고, False는 값이 존재한다는 의미입니다. 하지만 isnull()만으로는 결측치의 개수를 파악하기 어려우므로, sum() 메서드를 함께 사용합니다.
isnull().sum()은 각 컬럼별로 True의 개수, 즉 결측치의 개수를 계산합니다. 이는 Boolean 값이 True일 때 1, False일 때 0으로 처리되기 때문입니다. 결과를 보면 각 컬럼에 몇 개의 결측치가 있는지 한눈에 파악할 수 있으며, 결측치가 많은 컬럼은 추가적인 처리(삭제, 대체 등)가 필요할 수 있습니다.
3.3 GroupBy를 사용한 데이터 분석
GroupBy는 데이터를 그룹별로 나누어 집계하거나 변환하는 강력한 기능입니다. Gapminder 데이터에서 다양한 그룹화 분석을 수행해보겠습니다.
3.3.1 대륙별 평균 기대수명
대륙별 평균 기대수명을 분석하면 세계 각 지역의 건강 수준과 의료 인프라의 차이를 한눈에 파악할 수 있습니다. 이 분석은 사회경제적 발전 정도, 의료 서비스 접근성, 생활환경 등의 차이가 기대수명에 미치는 영향을 직관적으로 보여줍니다. 평균 기대수명이 낮은 대륙은 공중보건 정책과 지원이 더 필요할 수 있음을 시사하며, 대륙 간 건강 격차를 정량적으로 측정할 수 있습니다.
이 분석을 수행하기 위해서는 먼저 groupby("continent")를 사용하여 데이터를 대륙별로 그룹화합니다. 그룹화된 데이터에서 ["lifeExp"]로 기대수명 컬럼만 선택한 후, .mean()을 적용하여 각 대륙의 평균 기대수명을 계산합니다. 마지막으로 .sort_values(ascending=False)를 사용하여 평균 기대수명이 높은 순서대로 정렬하면, 어떤 대륙이 상대적으로 건강 수준이 높은지 쉽게 파악할 수 있습니다.
groupby() 연산의 결과는 Series 객체로 반환되며, 인덱스는 그룹화 기준(대륙명)이 되고 값은 집계 결과(평균 기대수명)가 됩니다. 이 구조는 대륙별 비교 분석에 매우 적합하며, 시각화나 추가 분석에도 용이합니다.
3.3.2 연도별 전체 인구 및 평균 기대수명
연도별로 전 세계 인구와 평균 기대수명을 함께 분석하면 시간에 따른 글로벌 트렌드를 통합적으로 이해할 수 있습니다. 인구 성장과 건강 수준의 변화를 동시에 관찰함으로써, 인구 증가가 기대수명에 미치는 영향이나 의료 발전이 인구 구조에 미치는 영향을 파악할 수 있습니다. 또한 전쟁, 전염병, 보건혁신 등의 주요 사건이 두 지표에 미친 영향을 시계열로 분석할 수 있습니다.
이 분석에서는 groupby("year")를 사용하여 데이터를 연도별로 그룹화한 후, agg() 메서드를 사용하여 여러 통계량을 한 번에 계산합니다. agg() 메서드는 딕셔너리 형태로 컬럼명과 집계 함수를 지정할 수 있어, 각 컬럼에 대해 서로 다른 집계 연산을 수행할 수 있습니다.
코드에서 total_pop=("pop", "sum")은 ‘pop’ 컬럼의 합계를 계산하여 ’total_pop’이라는 이름의 새 컬럼으로 저장합니다. 마찬가지로 ’mean_lifeExp=(“lifeExp”, “mean”)’은 기대수명의 평균을, ’median_gdpPercap=(“gdpPercap”, “median”)’은 1인당 GDP의 중앙값을 계산합니다. 중앙값을 사용하는 이유는 평균이 극단값에 의해 왜곡될 수 있기 때문입니다.
reset_index()를 호출하면 그룹화 기준이었던 ’year’가 인덱스에서 일반 컬럼으로 변환되어, 이후 분석이나 시각화에서 더 쉽게 사용할 수 있습니다. 이는 Tidy Data 원칙에 부합하는 구조로, 각 행이 하나의 연도를 나타내는 관측치가 됩니다.
3.3.3 대륙별 연도별 1인당 GDP 중앙값
대륙별 연도별 1인당 GDP를 분석하면 각 대륙의 경제 발전 추이와 대륙 간 경제적 격차의 변화를 시계열로 비교할 수 있습니다. 이 분석은 아시아, 아프리카, 유럽 등의 경제성장 패턴과 경제 격차의 축소 또는 확대 현상을 객관적으로 보여줍니다.
이 분석에서는 평균 대신 중앙값을 사용합니다. 그 이유는 1인당 GDP는 국가 간 격차가 매우 크기 때문에, 극단적으로 부유하거나 가난한 일부 국가가 평균을 크게 왜곡시킬 수 있기 때문입니다. 중앙값은 모든 값을 크기 순으로 나열했을 때 가운데 값이므로, 이상치의 영향을 받지 않아 대륙 전체의 대표적인 경제 수준을 더 공정하게 반영합니다.
코드에서 groupby(["continent", "year"])는 대륙과 연도를 동시에 기준으로 그룹화합니다. 이렇게 하면 각 대륙-연도 조합에 대해 집계가 수행됩니다. ["gdpPercap"]로 1인당 GDP 컬럼만 선택한 후 .median()을 적용하여 중앙값을 계산합니다.
reset_index()를 호출하면 MultiIndex가 일반 컬럼으로 변환됩니다. 그런데 컬럼명이 자동으로 생성되므로, columns 속성을 직접 설정하여 의미 있는 이름(median_gdpPercap)을 부여합니다. 이렇게 하면 이후 분석이나 시각화에서 컬럼을 참조할 때 더 명확합니다.
3.3.4 대륙별 기대수명 변화 추이
대륙별 기대수명의 시계열 변화를 분석하면 각 대륙의 건강 수준이 시간에 따라 어떻게 진화했는지, 그리고 대륙 간 건강 격차가 축소되었는지 확대되었는지를 정량적으로 파악할 수 있습니다. 이 분석은 보건 정책, 의료 인프라, 경제 발전 등이 기대수명에 미친 영향을 대륙별로 비교하는 데 중요한 근거를 제공합니다.
예를 들어, 아프리카와 유럽의 기대수명 격차가 시간이 지나며 좁혀졌다면 글로벌 건강 형평성 향상을 의미할 수 있고, 반대로 격차가 더욱 벌어졌다면 향후 개발 지원 정책의 방향 설정에 중요한 시사점을 제공합니다. 또한 특정 대륙에서 특정 시점 이후 급격한 변화가 관찰된다면, 그 배경에 있는 사회적·경제적 원인을 추가적으로 탐구할 수 있습니다.
코드 구조는 앞서 살펴본 대륙별 연도별 GDP 분석과 유사합니다. groupby(["continent", "year"])로 대륙과 연도를 동시에 기준으로 그룹화한 후, ["lifeExp"] 컬럼의 평균을 계산합니다. 평균을 사용하는 이유는 기대수명은 GDP와 달리 국가 간 격차가 상대적으로 작고, 중앙값과 평균의 차이가 크지 않기 때문입니다.
reset_index()로 MultiIndex를 일반 컬럼으로 변환한 후, 컬럼명을 명확하게 설정합니다. 이렇게 생성된 데이터프레임은 시각화에 매우 적합한 구조입니다. 예를 들어, sns.lineplot()을 사용하여 대륙별로 다른 색상의 선을 그려 시계열 추이를 비교할 수 있습니다.
3.3.5 국가별 기대수명 증가율 계산
국가별로 기대수명이 시간에 따라 얼마나 개선되었는지를 정량적으로 측정하면, 각 국가의 건강 수준 개선 정도를 비교할 수 있습니다. 이 분석은 의료 체계, 경제 성장, 보건 정책 등 다양한 요인이 국가별로 인류 건강 증진에 어떻게 기여했는지를 평가하는 데 중요한 근거를 제공합니다.
기대수명 증가율을 계산하기 위해서는 각 국가의 초기 기대수명과 최종 기대수명을 먼저 추출해야 합니다. groupby("country")로 국가별로 그룹화한 후, agg() 메서드를 사용하여 여러 통계량을 한 번에 계산합니다. ("year", "min")과 ("year", "max")는 각 국가의 데이터가 시작된 연도와 마지막 연도를 찾고, ("lifeExp", "first")와 ("lifeExp", "last")는 각 국가의 첫 번째와 마지막 기대수명 값을 추출합니다.
first와 last는 그룹 내에서 첫 번째와 마지막 값을 반환하는 집계 함수입니다. 데이터가 연도순으로 정렬되어 있다면, 이는 각 국가의 초기 기대수명과 최종 기대수명을 의미합니다. 만약 데이터가 정렬되어 있지 않다면, sort_values()를 먼저 적용해야 합니다.
증가율은 절대 증가량(last_lifeExp - first_lifeExp)과 상대 증가율((증가량 / 초기값) * 100) 두 가지 방식으로 계산할 수 있습니다. 절대 증가량은 실제로 몇 년이 늘었는지를 보여주고, 상대 증가율은 초기값 대비 얼마나 증가했는지를 백분율로 보여줍니다. 두 지표 모두 유용하지만, 초기 기대수명이 낮은 국가는 상대 증가율이 높게 나올 수 있으므로 두 지표를 함께 고려하는 것이 좋습니다.
nlargest(10, "lifeExp_increase")는 lifeExp_increase 컬럼을 기준으로 상위 10개 국가를 선택합니다. 이렇게 하면 기대수명이 많이 개선된 국가들을 쉽게 찾을 수 있습니다.
country_life_change = (
df.groupby("country")
.agg(
first_year=("year", "min"),
last_year=("year", "max"),
first_lifeExp=("lifeExp", "first"),
last_lifeExp=("lifeExp", "last"),
)
.reset_index()
)
country_life_change["lifeExp_increase"] = (
country_life_change["last_lifeExp"]
- country_life_change["first_lifeExp"]
)
country_life_change["lifeExp_pct_increase"] = (
country_life_change["lifeExp_increase"]
/ country_life_change["first_lifeExp"]
) * 100
top_increase = country_life_change.nlargest(
10, "lifeExp_increase"
)3.3.6 대륙별 통계 요약
대륙별로 여러 핵심 지표를 한 번에 요약하면 각 대륙의 전반적인 특성을 한눈에 비교할 수 있습니다. 국가 수, 평균 기대수명, 중앙값 GDP, 총인구 등의 지표를 함께 보면 대륙 간의 발전 정도, 경제적 격차, 인구 규모의 차이, 건강 수준 등을 통합적으로 파악할 수 있습니다. 이러한 요약 통계는 추후 대륙별 심층 분석의 기초가 되며, 전 세계적 맥락에서 각 대륙의 상대적 위치를 이해하는 데 중요한 근거를 제공합니다.
이 분석에서는 agg() 메서드를 사용하여 각 대륙에 대해 서로 다른 집계 연산을 동시에 수행합니다. ("country", "nunique")는 각 대륙에 포함된 고유 국가의 개수를 계산합니다. nunique는 ’number of unique’의 약자로, 중복을 제외한 고유값의 개수를 반환합니다. 이는 각 대륙이 몇 개의 국가로 구성되어 있는지를 보여줍니다.
("lifeExp", "mean")은 기대수명의 평균을, ("gdpPercap", "median")은 1인당 GDP의 중앙값을 계산합니다. GDP는 중앙값을 사용하여 이상치의 영향을 줄이고, 기대수명은 평균을 사용합니다. ("pop", "sum")은 각 대륙의 총 인구를 계산합니다. 이는 각 국가의 인구를 합산한 값으로, 대륙의 전체 인구 규모를 나타냅니다.
이렇게 생성된 요약 테이블은 각 행이 하나의 대륙을 나타내는 Tidy Data 형태로, 시각화나 추가 분석에 매우 적합한 구조입니다. 예를 들어, 이 데이터를 사용하여 대륙별 기대수명과 GDP의 관계를 산점도로 그릴 수 있습니다.
3.4 시각화
데이터를 시각화하면 숫자로만 표현된 통계량보다 패턴과 트렌드를 훨씬 직관적으로 파악할 수 있습니다. 시각화는 데이터의 숨겨진 관계를 발견하고, 분석 결과를 효과적으로 전달하는 데 필수적인 도구입니다. Matplotlib과 Seaborn을 사용하여 다양한 그래프를 그려보겠습니다.
3.4.1 선 그래프: 연도별 평균 기대수명 변화
시계열 데이터를 시각화할 때는 선 그래프가 적합합니다. 연도별 평균 기대수명의 변화를 선 그래프로 그리면 전 세계 건강 수준이 시간에 따라 어떻게 개선되었는지 한눈에 파악할 수 있습니다. 이 그래프는 의료 발전, 보건 정책, 경제 성장 등이 기대수명에 미친 영향을 시각적으로 보여줍니다.
sns.lineplot()은 Seaborn의 선 그래프 함수로, 데이터프레임을 직접 받아서 시각화할 수 있습니다. data 매개변수에 데이터프레임을, x와 y에 각각 x축과 y축에 사용할 컬럼명을 지정합니다. marker="o"는 각 데이터 포인트에 원형 마커를 표시하여 데이터가 어느 위치에 있는지 명확하게 보여줍니다.
plt.title(), plt.xlabel(), plt.ylabel()은 각각 그래프의 제목과 축 레이블을 설정합니다. 명확한 레이블은 그래프를 읽는 사람이 무엇을 보는지 쉽게 이해할 수 있게 해줍니다. plt.grid(True, alpha=0.3)는 격자를 표시하여 값을 읽기 쉽게 만듭니다. alpha=0.3은 격자의 투명도를 설정하여 격자가 데이터를 가리지 않도록 합니다. plt.tight_layout()은 레이블이 잘리지 않도록 레이아웃을 자동으로 조정합니다.
3.4.2 선 그래프: 대륙별 기대수명 변화 추이
여러 그룹의 시계열 데이터를 한 그래프에 그리면 그룹 간 비교가 용이합니다. 대륙별 기대수명 변화를 하나의 그래프에 그리면 각 대륙의 추이를 직접 비교할 수 있고, 대륙 간 격차가 시간에 따라 어떻게 변화했는지 시각적으로 파악할 수 있습니다.
hue="continent" 매개변수는 continent 컬럼의 값에 따라 다른 색상의 선을 그립니다. 각 대륙마다 고유한 색상이 할당되어 여러 선을 구분하기 쉽습니다. markersize=4는 마커의 크기를 조정하여 그래프가 복잡해 보이지 않도록 합니다.
plt.legend(title="대륙")은 범례를 표시하여 각 색상이 어떤 대륙을 나타내는지 알 수 있게 해줍니다. title 매개변수는 범례의 제목을 설정합니다. 범례가 없으면 색상만으로는 어떤 선이 어떤 대륙인지 구분하기 어려우므로, 범례는 필수적입니다.
이 그래프를 통해 각 대륙의 기대수명이 시간에 따라 어떻게 변화했는지, 그리고 대륙 간 격차가 축소되었는지 확대되었는지를 한눈에 확인할 수 있습니다. 예를 들어, 모든 대륙의 선이 위쪽으로 향하고 있다면 전반적인 건강 수준이 개선되고 있다는 의미이고, 선들 사이의 간격이 좁아지고 있다면 대륙 간 격차가 축소되고 있다는 의미입니다.
3.4.3 로그 스케일: 대륙별 1인당 GDP 변화
1인당 GDP는 국가 간 격차가 매우 크기 때문에 일반 선형 스케일로는 변화를 제대로 파악하기 어렵습니다. 예를 들어, GDP가 1,000달러에서 2,000달러로 증가하는 것과 10,000달러에서 11,000달러로 증가하는 것은 절대 증가량은 같지만 상대적 의미는 완전히 다릅니다. 로그 스케일을 사용하면 이러한 상대적 변화를 더 명확하게 볼 수 있습니다.
로그 스케일은 지수적 변화를 선형적으로 보이게 만들어 줍니다. GDP가 2배, 3배로 증가하는 것처럼 비율적 변화를 거리로 표현할 수 있어, 경제 성장률을 비교하는 데 매우 유용합니다. plt.yscale("log")는 y축을 로그 스케일로 변환합니다. 이렇게 하면 GDP가 낮은 대륙과 높은 대륙의 변화를 같은 그래프에서 비교하기 쉬워집니다.
plt.grid(True, alpha=0.3, which="both")에서 which="both"는 일반 격자와 로그 격자를 모두 표시합니다. 로그 스케일에서는 10의 거듭제곱(10, 100, 1000, 10000 등) 위치에 격자가 표시되어 값을 읽기 쉽게 해줍니다.
이 그래프를 통해 각 대륙의 경제 성장 패턴을 비교할 수 있습니다. 선의 기울기가 가파를수록 빠른 경제 성장을 의미하고, 선들 사이의 간격이 좁아지면 대륙 간 경제 격차가 축소되고 있다는 의미입니다.
sns.lineplot(
data=continent_year_gdp,
x="year",
y="median_gdpPercap",
hue="continent",
marker="o",
markersize=4,
)
plt.yscale("log") # 로그 스케일 적용
plt.title("대륙별 1인당 GDP 중앙값 변화 (로그 스케일)")
plt.xlabel("연도")
plt.ylabel("1인당 GDP 중앙값 (로그 스케일)")
plt.legend(title="대륙")
plt.grid(True, alpha=0.3, which="both")
plt.tight_layout()
plt.show()
3.4.4 산점도: 기대수명과 1인당 GDP의 관계
두 변수 간의 관계를 시각화할 때는 산점도가 적합합니다. 기대수명과 1인당 GDP의 관계를 산점도로 그리면 경제 수준과 건강 수준 사이의 상관관계를 직관적으로 파악할 수 있습니다. 일반적으로 GDP가 높을수록 기대수명도 높아지는 양의 상관관계가 있지만, 이 관계가 선형적인지, 어느 정도의 강도를 가지는지는 데이터를 시각화해야 알 수 있습니다.
먼저 특정 연도(2020년)의 데이터만 필터링합니다. 시계열 데이터를 한 시점으로 고정하면 해당 시점의 국가 간 관계를 명확하게 볼 수 있습니다. df[df["year"] == 2020]는 Boolean 인덱싱을 사용하여 2020년 데이터만 선택합니다. .copy()를 호출하는 것은 원본 데이터를 수정하지 않도록 명시적으로 복사본을 만드는 것입니다.
plt.scatter()는 산점도를 그리는 함수입니다. s 매개변수는 점의 크기를 지정하며, 여기서는 인구를 1,000,000으로 나눈 값을 사용하여 인구가 많은 국가일수록 큰 점으로 표시됩니다. 이렇게 하면 인구 규모가 큰 국가가 그래프에서 더 눈에 띄게 됩니다.
c 매개변수는 점의 색상을 지정합니다. astype("category").cat.codes는 대륙명을 범주형으로 변환한 후 숫자 코드로 변환합니다. 이 숫자 코드는 cmap="Set1" 컬러맵의 인덱스로 사용되어 각 대륙마다 다른 색상이 할당됩니다. alpha=0.6은 점의 투명도를 설정하여 겹치는 점들도 볼 수 있게 합니다.
plt.xscale("log")는 x축을 로그 스케일로 변환합니다. GDP는 국가 간 격차가 매우 크므로 로그 스케일을 사용하지 않으면 대부분의 점이 왼쪽에 몰려 보입니다. 로그 스케일을 사용하면 GDP가 낮은 국가와 높은 국가를 모두 명확하게 볼 수 있습니다.
plt.colorbar()는 색상과 대륙의 매핑을 보여주는 컬러바를 추가합니다. 이를 통해 각 색상이 어떤 대륙을 나타내는지 확인할 수 있습니다.
df_2020 = df[df["year"] == 2020].copy()
scatter = plt.scatter(
df_2020["gdpPercap"],
df_2020["lifeExp"],
s=df_2020["pop"]
/ 1000000, # 인구 크기에 비례한 점 크기
c=df_2020["continent"].astype("category").cat.codes,
alpha=0.6,
cmap="Set1",
)
plt.xscale("log") # GDP는 로그 스케일
plt.xlabel("1인당 GDP (로그 스케일)")
plt.ylabel("기대수명 (년)")
plt.title("기대수명 vs 1인당 GDP (2020년, 점 크기 = 인구)")
plt.colorbar(scatter, label="대륙")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
3.4.5 산점도 그래프: Seaborn을 활용
Seaborn의 scatterplot() 함수는 Matplotlib의 scatter()보다 더 간편하고 세련된 산점도를 그릴 수 있게 해줍니다. 특히 여러 변수를 동시에 시각화할 때 코드가 더 간결하고 읽기 쉽습니다.
plt.figure(figsize=(12, 8))는 그래프의 크기를 설정합니다. figsize는 (너비, 높이)를 인치 단위로 지정하며, 더 큰 그래프는 더 많은 정보를 명확하게 표시할 수 있습니다.
hue="continent"는 대륙별로 다른 색상을 자동으로 할당합니다. Seaborn은 범주형 데이터를 자동으로 인식하여 적절한 색상을 선택하므로, Matplotlib처럼 수동으로 범주형 코드를 변환할 필요가 없습니다.
size="pop"은 인구에 비례하여 점의 크기를 조정합니다. sizes=(50, 1000)은 점 크기의 최솟값과 최댓값을 지정합니다. 이렇게 하면 인구가 많은 국가일수록 큰 점으로 표시되어, 인구 규모를 시각적으로 표현할 수 있습니다.
bbox_to_anchor=(1.05, 1)와 loc="upper left"는 범례의 위치를 그래프 밖 오른쪽에 배치합니다. 이렇게 하면 범례가 데이터를 가리지 않으면서도 명확하게 보입니다.
이 그래프는 한 번에 세 가지 정보(기대수명, GDP, 인구)를 시각화하여 국가 간 관계를 다차원적으로 파악할 수 있게 해줍니다.
plt.figure(figsize=(12, 8))
sns.scatterplot(
data=df_2020,
x="gdpPercap",
y="lifeExp",
hue="continent",
size="pop",
sizes=(50, 1000),
alpha=0.7,
)
plt.xscale("log")
plt.xlabel("1인당 GDP (로그 스케일)")
plt.ylabel("기대수명 (년)")
plt.title("기대수명 vs 1인당 GDP (2020년)")
plt.legend(
title="대륙",
bbox_to_anchor=(1.05, 1),
loc="upper left",
)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
3.4.6 박스 플롯: 대륙별 기대수명 분포
그룹별 데이터의 분포를 비교할 때는 박스 플롯(Box Plot)이 매우 유용합니다. 박스 플롯은 중앙값, 사분위수, 이상치 등을 한눈에 보여주어 각 그룹의 분포 특성을 빠르게 파악할 수 있게 해줍니다. 대륙별 기대수명 분포를 박스 플롯으로 그리면 각 대륙 내 국가들의 기대수명이 얼마나 다양하고, 대륙 간 차이가 얼마나 큰지 시각적으로 비교할 수 있습니다.
박스 플롯의 각 요소는 특정 통계량을 나타냅니다. 박스의 중간 선은 중앙값(median)을, 박스의 아래쪽과 위쪽 경계는 각각 1사분위수(Q1)와 3사분위수(Q3)를 나타냅니다. 박스의 높이(사분위간 범위, IQR)는 데이터의 변동성을 보여줍니다. 수염(whisker)은 일반적으로 Q1 - 1.5 * IQR과 Q3 + 1.5 * IQR 범위 내의 데이터를 나타내며, 이 범위를 벗어나는 점은 이상치로 표시됩니다.
hue="continent"는 대륙별로 다른 색상을 지정하지만, 여기서는 x축도 대륙이므로 중복입니다. legend=False는 범례를 표시하지 않습니다. palette="Set2"는 색상 팔레트를 지정하여 각 대륙에 일관된 색상을 할당합니다.
plt.xticks(rotation=45)는 x축 레이블을 45도 회전시켜 긴 대륙명이 겹치지 않도록 합니다. axis="y"는 y축에만 격자를 표시하여 가로선만 그려 데이터를 읽기 쉽게 만듭니다.
이 그래프를 통해 각 대륙의 기대수명 분포가 얼마나 넓은지(박스의 높이), 대륙 간 중앙값 차이가 얼마나 큰지(박스의 위치), 그리고 각 대륙에 이상치가 있는지(박스 밖의 점)를 한눈에 파악할 수 있습니다.
3.4.7 선 그래프: 연도별 전체 인구 변화
전 세계 인구의 시계열 변화를 시각화하면 인구 성장 추이를 한눈에 파악할 수 있습니다. 인구는 매우 큰 숫자이므로 10억 명 단위로 나누어 표시하면 그래프를 읽기 쉽고, 인구 성장률의 변화도 더 명확하게 볼 수 있습니다.
plt.plot()은 Matplotlib의 기본 선 그래프 함수입니다. yearly_stats["total_pop"] / 1e9는 인구를 10억으로 나누어 단위를 변환합니다. 1e9는 과학적 표기법으로 10의 9제곱(1,000,000,000)을 의미합니다. marker="o"는 각 데이터 포인트에 원형 마커를 표시하고, linewidth=2는 선의 두께를 설정하여 그래프를 더 명확하게 만듭니다.
이 그래프를 통해 전 세계 인구가 시간에 따라 어떻게 증가했는지, 증가 속도가 일정한지 아니면 가속화되었는지 등을 파악할 수 있습니다. 선의 기울기가 가파를수록 빠른 인구 증가를 의미합니다.
3.4.8 히트맵: 대륙별/연도별 평균 기대수명
히트맵(Heatmap)은 두 개의 범주형 변수와 하나의 수치형 변수를 색상 강도로 표현하는 시각화 방법입니다. 대륙별 연도별 평균 기대수명을 히트맵으로 그리면 각 대륙의 기대수명이 시간에 따라 어떻게 변화했는지를 색상의 변화로 한눈에 파악할 수 있습니다. 어두운 색은 낮은 값을, 밝은 색은 높은 값을 나타내어 패턴을 빠르게 인식할 수 있습니다.
히트맵을 그리기 위해서는 먼저 데이터를 피벗 테이블 형태로 변환해야 합니다. pivot() 메서드는 Long 형식의 데이터를 Wide 형식으로 변환합니다. index="continent"는 행 인덱스를 대륙으로, columns="year"는 열을 연도로, values="mean_lifeExp"는 셀의 값을 기대수명으로 설정합니다. 이렇게 변환하면 각 행이 하나의 대륙을, 각 열이 하나의 연도를 나타내는 2차원 테이블이 생성됩니다.
sns.heatmap()은 이 피벗 테이블을 히트맵으로 시각화합니다. cmap="YlOrRd"는 Yellow-Orange-Red 컬러맵을 사용하여 낮은 값은 노란색, 높은 값은 빨간색으로 표시합니다. cbar_kws={"label": "평균 기대수명 (년)"}는 컬러바의 레이블을 설정하여 색상이 어떤 값을 나타내는지 명확하게 합니다.
히트맵을 통해 각 대륙의 기대수명이 시간에 따라 어떻게 변화했는지, 그리고 대륙 간 격차가 어떻게 변화했는지를 색상의 패턴으로 직관적으로 파악할 수 있습니다. 예를 들어, 모든 대륙의 색상이 시간이 지나면서 밝아진다면 전반적인 건강 수준이 개선되고 있다는 의미입니다.