데이터를 분석할 때 적절한 그래프를 그리는 것은 매우 중요하다.
데이터들간의 상관간계가 명확하지 않을 때 데이터를 시각화하는 것은 좋은 방법이다.
seaborn 라이브러리에 속한 anscombe 데이터집합은 4개의 데이터타입 모두 같은 수칫갑이나 상관관계, 회귀선을 가진다.
그렇기 떄문에 데이터의 시각화를 통해 다른 데이터임을 확인하는 대표적인 사례이다.
다음을 살펴보자.
import seaborn as sns
anscombe = sns.load_dataset("anscombe")
print(anscombe.head(), '\n')
print(type(anscombe))
dataset x y
0 I 10.0 8.04
1 I 8.0 6.95
2 I 13.0 7.58
3 I 9.0 8.81
4 I 11.0 8.33
<class 'pandas.core.frame.DataFrame'>
데이터를 가져왔지만 어떤 데이터인지 명확하게 보이지 않는다.
먼저 데이터마다 어떤 공통점이 있는지 간단하게 알아보자.
for i in range(4): # 4개의 데이터 모두 같은 평균을 가짐.
print(globals()['dataset_'+str(i+1)].mean()) # globals는 반복문에서 전역변수로 만들어줌. 이거 매우 유용!
x 9.000000
y 7.500909
dtype: float64
x 9.000000
y 7.500909
dtype: float64
x 9.0
y 7.5
dtype: float64
x 9.000000
y 7.500909
dtype: float64
모두 같은 평균값을 갖는 것을 알 수 있다.
평균뿐만 아니라 분산, 상관계수, 선형회귀선, 선형회귀 결정계수까지 모두 같다고 한다.
이제 데이터를 시각화해보자.
import matplotlib.pyplot as plt
dataset_1 = anscombe[anscombe['dataset']=='I']
plt.plot(dataset_1['x'], dataset_1['y'])
[<matplotlib.lines.Line2D at 0x7fb7541487f0>]
좀 더 쉽게 보기 위해 scatter를 사용할 수 있다.
plt.scatter(dataset_1['x'], dataset_1['y']) # 또는 plt.plot(x, y, 'o')를 이용해서 점으로 plot 가능.
<matplotlib.collections.PathCollection at 0x7fb6f00673a0>

이제 4개의 데이터셋에 대해 모두 scatter를 이용해 plot해보자.
fig = plt.figure()
fig.suptitle("Anscombe Data") # 전체 제목 설정
axes1 = fig.add_subplot(2, 2, 1)
axes2 = fig.add_subplot(2, 2, 2)
axes3 = fig.add_subplot(2, 2, 3)
axes4 = fig.add_subplot(2, 2, 4) # subplot을 plt.subplot이 아닌 fig.add_subplot도 가능함.
dataset_1 = anscombe[anscombe['dataset']=='I'] # 필요한 data 추출.
dataset_2 = anscombe[anscombe['dataset']=='II']
dataset_3 = anscombe[anscombe['dataset']=='III']
dataset_4 = anscombe[anscombe['dataset']=='IV']
axes1.plot(dataset_1['x'], dataset_1['y'], 'o') # data별로 scatter해줌.
axes2.plot(dataset_2['x'], dataset_2['y'], 'o')
axes3.plot(dataset_3['x'], dataset_3['y'], 'o')
axes4.plot(dataset_4['x'], dataset_4['y'], 'o')
axes1.set_title("dataset_1") # subplot마다 제목 지정.
axes2.set_title("dataset_2")
axes3.set_title("dataset_3")
axes4.set_title("dataset_4")
fig.tight_layout() # 제목과 숫자가 겹치는 것을 방지.
다양한 값들이 같은 데이터들이지만 그래프를 그려보니 완전히 다른 데이터셋이라는 것이 드러났다.
데이터 시각화의 중요성을 알 수 있다.
이제 데이터를 시각화하는 다양한 방법들을 알아보자.
먼저 그래프를 그리기 위해 seaborn 라이브러리내에 속한 tips라는 데이터집합을 가져온다. 이는 어떤 식당에서 팁을 지불한 손님의 정보를 모아둔 것이다.
tips = sns.load_dataset("tips")
print(tips.head())
print(type(tips))
print(tips.shape)
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
<class 'pandas.core.frame.DataFrame'>
(244, 7)
단변량 그래프인 히스토그램부터 그려보자.
# 히스토그램은 변수 하나만 사용해서 그리기 때문에 '일변량 그래프'라고도 한다.
plt.figure()
plt.hist(tips['total_bill'], bins=10)
plt.title('Histogram of Total Bill')
plt.xlabel('Frequency')
plt.ylabel('Total Bill')
Text(0, 0.5, 'Total Bill')

이처럼 plt.hist를 이용하여 쉽게 그릴 수 있다.
좀 더 복잡한 다변량 그래프들을 그려보자.
scatter를 이용하여 산점도 그래프를 그릴 수 있다.
plt.figure()
plt.scatter(tips['total_bill'], tips["tip"], c='black')
plt.xlabel('Total Bill')
plt.ylabel('Tip')
Text(0, 0.5, 'Tip')
그래프를 통해 전체 요금이 클수록 팁의 금액도 커지는 경향성을 한 눈에 볼 수 있다.
다음으로 박스 그래프를 그려보자.
'박스 그래프'는 이산형 변수와 연속형 변수를 사용한다.
이산형 변수 = Male, Female처럼 명확하게 구분되는 변수
연속형 변수 = Tip처럼 명확하게 셀 수 없는 범위의 값을 가지는 변수
boxplot = plt.figure()
axes1 = boxplot.add_subplot(1,1,1)
axes1.boxplot([tips[tips['sex'] == 'Female']['tip'],
tips[tips['sex'] == 'Male']['tip']], # x, y값을 []로 묶어줘야 하는 게 특이점.
labels=['Female', 'Male'])
axes1.set_xlabel('Sex', fontsize=15)
axes1.set_ylabel('Tip', fontsize=15)
Text(0, 0.5, 'Tip')

박스 플랏은 통계에서 자주 쓰이며 이상치를 찾을 때 유용하다.
간단하게 알아보자.
정확한 명칭은 box-and-whisker plot 이다.
박스 안의 빨간 선 = 중앙값(median)
박스(Box) = 25%(Q1) ~75%(Q3) 까지 값들을 둘러싼 박스
Q1 = 박스의 아래끝
Q3 = 박스의 위쪽 끝
수염(whiskers) = 박스의 각 모서리 (Q1, Q3)로 부터 IQR의 1.5배 내에 있는 가장 멀리 떨어진 데이터 점까지 이어진 것
Inter Quartile range(IQR) = Q3 - Q1
이상치(outlier) = 중앙값으로부터 전체의 2/3 밖의 값
박스 플랏의 단점도 존재한다.
바로 분산이 명확하게 보이지 않는다는 점이다.
violinplot을 사용하면 분산을 명확하게 확인할 수 있다.
ax = plt.subplots()
ax = sns.violinplot(x='time', y='total_bill', data=tips)
# boxplot과 달리 분산도 잘 표현함.
hue = 'sex'를 코드에 추가하여 성별에 따라 색상을 다르게 할 수도 있다.
이때, split = 'True'를 추가하면 한 눈에 보기 쉽게 그릴 수도 있다.
이제 3개 이상의 변수를 다뤄보자.
scatter의 사이즈를 tips에서 테이블당 인원수를 의미하는 size에 10을 곱하여 차이가 잘 드러나도록 하고
color에 tips의 sex_color를 입력하여 Male과 Female이 구분 가능하도록 하였다.
임의의 함수를 만들어 tips[sex]에 브로드캐스팅하자.
apply함수를 이용하면 열 전체에 지정한 함수를 브로드캐스팅할 수 있다.
def sex_to_color(sex):
if(sex=='Female'):
return 0
else:
return 1 # color에 0과 1을 넣어도 되는 것을 참고.
tips['sex_color'] = tips['sex'].apply(sex_to_color)
# apply함수를 이용하여 sex_to_color함수로 tips데이터프레임 전체에 브로드캐스팅함.
print(tips.head())
total_bill tip sex smoker day time size sex_color
0 16.99 1.01 Female No Sun Dinner 2 0
1 10.34 1.66 Male No Sun Dinner 3 1
2 21.01 3.50 Male No Sun Dinner 3 1
3 23.68 3.31 Male No Sun Dinner 2 1
4 24.59 3.61 Female No Sun Dinner 4 0
이제 그림을 그리면 다음과 같이 성별에 따라 색깔을 지정할 수 있다.
plt.figure()
plt.scatter(tips['total_bill'], tips["tip"], s=tips['size']*10, c=tips['sex_color'], alpha=0.5)
plt.xlabel('Total Bill')
plt.ylabel('Tip')
Text(0, 0.5, 'Tip')
sns.scatterplot에서는 hue를 지정하여 색깔을 구분할 수 있다.
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="sex", size='size')
그래프 중에는 밀집도 그래프도 있다.
이는 히스토그램을 그리고 그 위에 피팅된 함수를 같이 그리는 것이다.
마찬가지로 tips 데이터를 가져와 그려보자.
distplot을 이용하여 쉽게 그려줄 수 있다.
이때, kde는 따로 설정하지 않으면 기본값이 True이고 False로 지정하면 밀집도 그래프가 없어진다.
import seaborn as sns
import matplotlib.pyplot as plt
tips = sns.load_dataset("tips")
plt.figure(figsize=(15, 8))
ax = plt.subplot(1,2,1)
ax = sns.distplot(tips['total_bill'])
ax.set_title('Total Bill Histogram with Density Plot')
ax2 = plt.subplot(1,2,2)
ax2 = sns.distplot(tips['total_bill'], kde=False) # kde=False로 하면 밀집도 그래프를 없앨 수 있다.
ax2.set_title('Total Bill Histogram')
Text(0.5, 1.0, 'Total Bill Histogram')
반대로 hist = False를 하면 히스토그램을 없앨 수 있다.
ax = plt.subplots()
ax = sns.distplot(tips['total_bill'], hist=False) # 히스토그램을 제거해줌.
ax.set_title('Total Bill Histogram with Density Plot')
Text(0.5, 1.0, 'Total Bill Histogram with Density Plot')
rug = True를 통해 히스토그램 밑에 density를 수평으로 보여주는 양탄자(?)그래프를 그릴 수 있다.
앞에 두 인자와는 달리 기본값이 False임을 알 수 있다.
ax = plt.subplots()
ax = sns.distplot(tips['total_bill'], rug=True) # 양탄자그래프(?)를 추가해 줌.
ax.set_title('Total Bill Histogram with Density Plot')
원하는 데이터의 개수를 알아서 구한 다음 plot까지 해주는 함수가 있다. 바로 countplot이다.
ax = plt.subplots()
ax = sns.countplot('day', data=tips)
ax.set_title('Count of days')
countplot의 tips는 내가 표현하고자 하는 데이터프레임, 'day'는 그 데이터프레임의 열이름이다.
즉, day별로 dataframe내의 데이터 개수를 나타낸 그래프이다.
산점도 그래프에 대해서 회귀선을 그리는 것도 가능하다.
regplot을 이용하면 앞과 비슷한 방법으로 그릴 수 있다.
ax = plt.subplots()
ax = sns.regplot(x = 'total_bill', y = 'tip', data=tips, color = 'orange')
ax2 = plt.subplots()
ax2 = sns.regplot(x = 'total_bill', y = 'tip', data=tips, fit_reg=False) # fit_reg를 False로 설정하여 회귀선 제거.
때로는 여러 그래프를 한 번에 그리고 싶을 때가 있다.
이럴 때 쓰는 것이 jointplot이다.
이를 이용하면 산점도그래프와 히스토그램을 동시에 그릴 수 있다.
코드는 다음과 같다.
# jointplot은 산점도그래프와 히스토그램을 동시에 그려줌. 여러 그래프를 동시에 그리는 장점.
joint = sns.jointplot(x='total_bill', y='tip', data=tips, label='pearsonr=0.68, p=6.7e-34')
joint.set_axis_labels(xlabel='Total Bill', ylabel='Tip')
joint.fig.suptitle('Joint Plot of Total Bill and Tip', fontsize=10)
Text(0.5, 0.98, 'Joint Plot of Total Bill and Tip')
scatter의 경우 아무래도 데이터의 수가 많을수록 점이 겹쳐 보이는 단점이 있다.
jointplot에서 kind='hex'로 하여 육각 그래프를 그리면 데이터의 수를 색의 명도 차이를 통해 알 수 있다.
hexbin = sns.jointplot(x='total_bill', y='tip', kind='hex', data=tips, label='pearsonr=0.68, p=6.7e-34')
hexbin.set_axis_labels(xlabel='Total Bill', ylabel='Tip')
hexbin.fig.suptitle('Hexbin Joint Plot of Total Bill and Tip', fontsize=10)
Text(0.5, 0.98, 'Hexbin Joint Plot of Total Bill and Tip')

이차원 밀집도(고등선)를 그려 분포를 파악하는 방법도 있다.
이때, shade = True를 사용하여 음영을 줄 수도 있다.
ax = plt.subplots()
ax = sns.kdeplot(data=tips['total_bill'],data2=tips['tip']) # data1은 x축, data2는 y축
pairplot을 이용하여 관계 그래프를 그릴 수 있다.
하지만 중복된 그래프가 그려지는 단점이 존재한다.
fig = sns.pairplot(tips) # 중복된 그래프가 포함되는 단점이 있음.
따라서 임의로 조정해줄 필요가 있다.
아래의 코드와 그림은 예시다.
pair_grid = sns.PairGrid(tips)
pair_grid = pair_grid.map_upper(sns.regplot) # 좌상단->우하단 대각선을 기준으로 상단을 regplot으로 그림.
pair_grid = pair_grid.map_lower(sns.kdeplot) # 대각선을 기준으로 하단을 kdeplot으로 그림.
pair_grid = pair_grid.map_diag(sns.distplot, rug=True) # 대각선을 distplot, rug=True로 그림.
plt.show()
여러 개의 plot을 동시에 수행하는 또다른 함수가 있다.
바로 seaborn 라이브러리의 lmplot이다.
다음과 같이 쉽게 plot이 가능하다. s를 tips['size']로 설정하여 점들의 크기를 조정해주었다.
sns.lmplot(x='total_bill', y='tip', data=tips, fit_reg=False, scatter_kws={'s': tips['size'].mul(10)})
한 가지 주의할 점은 hue를 지정하면 plot이 되지 않는다는 것이다.
다음과 같은 오류가 발생한다.
size = tips['size']*10
scatter = sns.lmplot(x='total_bill', y='tip', data=tips, hue='sex', fit_reg=False, scatter_kws={'s': tips['size']})
plt.show()
ValueError: s must be a scalar, or float array-like with the same size as x and y
lmplot은 hue마다 개별의 plot을 수행하는데 matplotlib kwargs는 전체를 한 번에 plot하기 때문에 data의 길이가 달라지는 것은 아닐까 하고 추측한다.
바로 위에서 hue가 없을 때 코드가 정상적으로 작동하였기 때문에 추측이 맞다고 생각한다.
누군가 안다면 알려주세요!
이제 앞에서 사용한 anscombe 데이터를 다시 불러와 lmplot으로 그려보자.
다음의 코드를 통해 쉽게 할 수 있다.
anscombe = sns.load_dataset('anscombe')
anscombe_plot = sns.lmplot(x='x', y='y', data=anscombe, fit_reg=False,
col='dataset', col_wrap=3)
col_wrap은 열의 최대수를 지정해준다. 3으로 지정했기 때문에 4번째 그림은 새로운 행에 그려진다.
col='dataset'은 dataset이라는 열의 종류로 나눠서 plot함을 의미한다.
지금까지는 어떤 함수를 이용하여 그래프를 그렸는데 데이터프레임과 시리즈에 바로 그래프를 그릴 수도 있다.
다음의 예를 보자.
fig, ax = plt.subplots()
ax = tips[['total_bill', 'tip']].plot.hist(alpha=0.5, bins=20, ax=ax)
시리즈에 함수를 이용하여 바로 그래프를 그렸다.
지금까지 그린 모든 그래프를 다음의 방법으로도 그릴 수 있다.
추가로 알아두면 좋은 것들을 몇 가지만 적고 포스팅을 마친다.
* 반복문에서 enumerate 메서드로 style 할당하기
따라해보면 도움이 많이 된다.
fig = plt.figure(figsize=(15, 6))
seaborn_styles = ['darkgrid', 'whitegrid', 'dark', 'white', 'ticks']
for idx, style in enumerate(seaborn_styles): # enumerate를 이용하여 각 인덱스와 원소를 idx와 style이라는 변수에 할당해줌. 매우 유용!
plot_position = idx + 1
with sns.axes_style(style):
ax = fig.add_subplot(2, 3, plot_position)
violin = sns.violinplot(x='time', y='total_bill', data=tips, ax=ax)
violin.set_title(style)
fig.tight_layout()
* FacetGrid
열과 행에 각각 변인을 지정하여 grid를 나눌 수 있다.
다음의 예를 살펴보자.
facet = sns.FacetGrid(tips, col='time', row='smoker', hue='sex') # 열과 행을 동시에 나누는 것도 가능!
facet.map(plt.scatter, 'total_bill', 'tip')
facet = facet.add_legend()
'Pandas' 카테고리의 다른 글
[Do it! Pandas] 7. 깔끔한 데이터 (0) | 2023.02.12 |
---|---|
[Do it! Pandas] 6. 누락값 처리하기 (0) | 2023.02.12 |
[Do it! Pandas] 5. 데이터 연결하기 (0) | 2023.02.12 |
[Do it! Pandas] 데이터프레임과 시리즈, 브로드캐스팅 (0) | 2023.02.09 |
[Do it! Pandas] 데이터프레임과 시리즈, 간단한 그래프 그리기 (0) | 2023.02.08 |