
Python 3.10.2
conda 24.9.0
JupyterLab 4.2.5
위키독스에서 제공하는 연습문제인 식당의 팁에 관련한 실습을 진행해보자.
먼저 필요한 라이브러리를 설치하자.
import numpy as np
import pandas as pd
import seaborn as sns # 통계 전용 시각화(matplotlib과 같이 사용 가능)
import matplotlib.pyplot as plt # 그래프 및 출력 옵션
import matplotlib as mpl # 환경설정(한글, -옵션 등)
mpl.rc("font", family="Malgun Gothic") # 한글 깨짐 방지
이 데이터셋은 seaborn에서 제공하는 데이터셋으로 깨끗한 데이터셋이다.
그리고 바로 load_dataset("tips")으로 사용할 수 있다.
이는 식당의 손님들이 주는 팁에 관련한 데이터로, 자세한 구조를 확인하자.
tips=sns.load_dataset("tips")
print(tips.shape)
print(tips.head())
(244, 7)
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
각 특성은 다음과 같다.
그리고 데이터를 시각화 하기전에 무엇부터? 통계량 부터 구해야한다.
따라서 데이터의 타입과 결측치는 있는지 등을 확인하기 위해 info() 함수를 찍어보자.
tips.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 244 entries, 0 to 243
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 total_bill 244 non-null float64
1 tip 244 non-null float64
2 sex 244 non-null category
3 smoker 244 non-null category
4 day 244 non-null category
5 time 244 non-null category
6 size 244 non-null int64
dtypes: category(4), float64(2), int64(1)
memory usage: 7.4 KB
총 244개(0번지부터 243번지)의 데이터로,
null값은 없고
그런데 object는 배웠는데(문자, 리스트, ...) category는? 문자 전용이다.
문자열 데이터 고유 값(unique)을 말한다.
이 카테고리는 웹에서의 카테고리 형태를 말한다. 즉 카테고리는 선택하는 것이기 때문에, 겹칠 수 없다.
대신 접근하는 방식이 조금 다르다.
이제 연속형(숫자형)의 통계량을 확인해보면,
tips.describe().T

그리고 범주형(문자형)의 통계량은,
tips.describe(include="category").T

이렇게 문자형일때 사용하던 include="object"가 아닌 category를 사용해주면 된다.
그런데 day와 time이 생각했던 월~일까지 7이 나오고, 아침,점심,저녁의 3이 나와야하는데 조금 다르다. 따라서 value_counts()를 찍어보자.
tips["day"].value_counts()
day
Sat 87
Sun 76
Thur 62
Fri 19
Name: count, dtype: int64
tips["time"].value_counts()
time
Dinner 176
Lunch 68
Name: count, dtype: int64
이렇게 확인해보니 이 식당은 목, 금, 토, 일만 운영하고,
점심과 저녁에만 운영한다는 것을 확인할 수 있었다.
항상 어떤 데이터든
크기 확인, 행렬 확인, 열의 타입 확인, 숫자통계량 확인, 문자통계량 확인, 데이터가 이해 안되면 value_counts()로 눈으로 확인
이런 순서를 지켜주면 좋다.
이렇게 파악이 된 후에 시각화를 하는 것이다.
빈도수: 성별 빈도수, 흡연 여부 빈도수, 요일 빈도수, 식사 타임 빈도수
그룹화(groupby, pivot_table): 성별 흡연 여부 빈도수, 요일별 식사 타임 방문자수
앞서 범주형(문자형)의 통계량은 아래와 같았다.
tips.describe(include="category").T

빈도수
이를 활용하여 빈도수를 확인하자.
print(tips["sex"].value_counts())
print(sns.countplot(data=tips, x="sex"))
sex
Male 157
Female 87
Name: count, dtype: int64
Axes(0.125,0.11;0.775x0.77)

print(tips["smoker"].value_counts())
print(sns.countplot(data=tips, x="smoker"))
print(tips["smoker"].value_counts())
print(sns.countplot(data=tips, x="smoker"))
smoker
No 151
Yes 93
Name: count, dtype: int64
Axes(0.125,0.11;0.775x0.77)

print(tips["day"].value_counts())
print(sns.countplot(data=tips, x="day"))
day
Sat 87
Sun 76
Thur 62
Fri 19
Name: count, dtype: int64
Axes(0.125,0.11;0.775x0.77)

이번에는 총 요일이 4개니까 만약 비율로 보고 싶다면?
plt.pie(tips["day"].value_counts(),
labels=tips["day"].value_counts().index,
autopct="%.1f%%",
explode=[0, 0.1, 0.15, 0.2])
plt.show()

print(tips["time"].value_counts())
print(sns.countplot(data=tips, x="time"))
time
Dinner 176
Lunch 68
Name: count, dtype: int64
Axes(0.125,0.11;0.775x0.77)

그룹화
성별과 흡연 여부를 그룹으로 묶고 빈도수(크기)를 구하는 size() 함수를 사용하면 된다.
먼저 groupby로 묶으면 다음과 같다.
tips.groupby(["smoker","sex"], observed=False).size()
smoker sex
Yes Male 60
Female 33
No Male 97
Female 54
dtype: int64
여기서 보면 observed=False라는 옵션을 추가해줬는데, 이 명령 없이 실행하면 값은 나오되 경고 메세지가 나온다.
성별이 남/녀가 있을때, 남자 50, 여자 60명을 그룹화를 다 해준다.
그런데 카테고리는 드롭다운 메뉴와 같이 보여지는 것만 그룹화 해준다. 따라서 먼저 남자만 따로 그룹화, 여자만 따로 그룹화를 할 수 있다.
즉 object 형은 겹칠 수 있어서 상관없지만, category 형은 유일한 값이다.
카테고리형 변수에서 나타나는 모든 카테고리를 그룹화하라는 명령이다.
(디폴트는 True다.)
이를 pivot_table로 표현하면 다음과 같다.
tips.pivot_table(index="smoker", columns="sex", aggfunc="size", observed=False)

이를 시각화 해주자.
sns.countplot(data=tips, x="sex", hue="smoker")

tips.groupby(["day", "time"], observed=False).size()
day time
Thur Lunch 61
Dinner 1
Fri Lunch 7
Dinner 12
Sat Lunch 0
Dinner 87
Sun Lunch 0
Dinner 76
dtype: int64
tips.pivot_table(index="day", columns="time", aggfunc="size", observed=False)

시각화 해주자.
sns.countplot(data=tips, x="day", hue="time")

분포의 모양, 확률밀도
식사 요금, 팁, 식사 인원
연속형(숫자형)의 통계량을 다시 확인해보면,
tips.describe().T

histplot
sns.histplot(data=tips, x="total_bill", kde=True, bins=25)

sns.histplot(data=tips, x="tip", kde=True)

sns.histplot(data=tips, x="size", kde=True)

2명이 가장 많이 온다는 뜻이다.
boxplot
boxplot으로 이상치를 확인할 수도 있다.
sns.boxplot(data=tips, x="total_bill")

집단간 통계량을 비교할 때 많이 사용
성별에 따른 팁 차이, 흡연 여부에 따른 팁 차이, 요일에 따른 팁차이, 식사 타임에 따른 팁 차이
범주형(문자형)의 통계량은 아래와 같았다.
tips.describe(include="category").T

그룹바이나 피봇테이블로 묶고 시각화하면 된다.
(나중에는 이를 검증해야한다. 그게 진짜인지. 그걸 나중에 배울 예정)
성별에 따른 팁 차이
tips.groupby("sex", observed=False)["tip"].mean()
sex
Male 3.089618
Female 2.833448
Name: tip, dtype: float64
tips.pivot_table(index="sex", values="tip", aggfunc="mean", observed=False)

sns.barplot(data=tips, x="sex", y="tip", errorbar=None) # 신뢰구간 표시 X

흡연 여부에 따른 팁 차이
tips.groupby("smoker", observed=False)["tip"].mean()
smoker
Yes 3.008710
No 2.991854
Name: tip, dtype: float64
tips.pivot_table(index="smoker", values="sex", aggfunc="mean", observed=False)

sns.barplot(data=tips, x="smoker", y="tip", errorbar=None)

이렇게 보면 거의 차이가 없다는 것을 알 수 있는데, 이건 주관적인 생각이다. 이걸 통계적으로 수치화하는 것이 검증이다.
요일에 따른 팁 차이
print(tips.groupby("day", observed=False)["tip"].mean(), "\n")
print(tips.pivot_table(index="day", values="tip", aggfunc="mean", observed=False))
day
Thur 2.771452
Fri 2.734737
Sat 2.993103
Sun 3.255132
Name: tip, dtype: float64
tip
day
Thur 2.771452
Fri 2.734737
Sat 2.993103
Sun 3.255132
sns.barplot(data=tips, x="day", y="tip", errorbar=None)

식사 타임에 따른 팁 차이
print(tips.groupby("time", observed=False)["tip"].mean(), "\n")
print(tips.pivot_table(index="time", values="tip", aggfunc="mean", observed=False))
time
Lunch 2.728088
Dinner 3.102670
Name: tip, dtype: float64
tip
time
Lunch 2.728088
Dinner 3.102670
sns.barplot(data=tips, x="time", y="tip", errorbar=None)

검증, 검정까지는 똑같이 가는데, 그 차이가 나는 이유가 뭔지 요인을 분석하는 것은 데이터 분석이고, 미래를 예측하는 것이 머신러닝이다.
식사 요금에 따른 팁 차이
식사 인원수에 따른 팁 차이
상관분석, 상관계수, 공분산행렬
숫자를 사용해야하기 때문에 먼저 number형을 가져온다.
tips_num=tips.select_dtypes(include="number")
tips_num.head()

실제로는 성별도 남성은 0, 여성은 1로 바꾸고, 1,000원과 같은 내용도 1000으로 바꿔주는 등의 모든 문자를 숫자로 바꿔주는 전처리 과정을 거친 후 상관분석을 진행해야하는데, 우선은 이미 number형인 내용을 가져다가 사용한다.
이제 이들의 상관관계를 보자면,
tips_num.corr()

이 공분산 행렬을 보면, 상관게수가 거의 0.5 언저리다.
원래 0.5 이하면 그냥 버리라고 했는데 거의 다 0.5 언저리니까 그냥 진행하자.
그나마 tip과 total_bill사이의 상관관계가 가장 강력함을 알 수 있다.
즉 식사 요금에 따른 팁 차이를 heatmap으로 보면,
sns.heatmap(tips_num.corr(), annot=True) # annotation True

이를 산점도(scatterplot)로 보더라도 거의 비슷함을 알 수 있다.
sns.scatterplot(data=tips, x="tip", y="total_bill")

이렇게 팁을 많이 주는 사람들일수록 식사 요금이 많이 나온다.
반대로 말하면 식사 요금이 많이 나오는 사람들이 팁을 많이 준다는 뜻이다.
여기에 lmplot으로 회귀선을 그려보면
sns.lmplot(data=tips, x="tip", y="total_bill")

그러면 이번에는 조금 낮은 상관관계를 가진 tip과 size,
즉 식사 인원수에 따른 팁 차이를 보면,
sns.lmplot(data=tips, x="tip", y="size")

이와 같이 선을 그어주기가 에매하다.
그래서 실제로 예측할때 이런 데이터들을 가지고 예측하면 예측률이 떨어진다.
별거 아닌거면 상관이 없는데, 만약 제조업의 불량과 정상을 구분하는데 예측률이 90%여도, 10% 정도의 차이가 나면 큰일난다.
그래서 이런 예측을 하는데 예측률이 계속 크게 벗어난다면, 또는 상관관계가 거의 다 0.5언저리라면 그런 데이터들을 잘못된 데이터인 것이다.
https://data.kma.go.kr/data/grnd/selectAsosRltmList.do?pgmNo=36
이번에는 기상청에서 날씨 데이터를 다운로드 받아서 분석해보자.
검색조건은 다음과 같다.

이렇게 2024년도 서울시의 기온 데이터를 csv파일로 다운받자.
시작 전 필요한 라이브러리 import먼저 하자.
import numpy as np
import pandas as pd
import seaborn as sns # 통계 전용 시각화(matplotlib과 같이 사용 가능)
import matplotlib.pyplot as plt # 그래프 및 출력 옵션
import matplotlib as mpl # 환경설정(한글, -옵션 등)
mpl.rc("font", family="Malgun Gothic") # 한글 깨짐 방지
plt.rcParams["axes.unicode_minus"]=False # 마이너스 깨짐 방지
이제 다운받은 csv파일을 불러오자.
weather=pd.read_csv(".\data\pandas\KMA_Seoul_2024010120241231.csv", encoding="cp949")
weather.shape
(366, 8)
1년간의 데이터인데 366인 이유는 2024년에는 2월이 29일까지 있었기 때문이다.
weather

weather.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 366 entries, 0 to 365
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 지점 366 non-null int64
1 지점명 366 non-null object
2 일시 366 non-null object
3 평균기온(°C) 366 non-null float64
4 최저기온(°C) 366 non-null float64
5 최저기온 시각(hhmi) 366 non-null int64
6 최고기온(°C) 366 non-null float64
7 최고기온 시각(hhmi) 366 non-null int64
dtypes: float64(3), int64(3), object(2)
memory usage: 23.0+ KB
weather.describe().T

이렇게 구조를 파악했으나 object형을 먼저 따로 보면,
weather.describe(include="object").T

이렇게 일시(날짜)를 object형에서 확인가능하다.
그럼 이제 일자별 평균 기온, 최저 기온, 최고 기온을 시각화해서 보고싶다면
먼저 일시 데이터를 년, 월, 일로 바꿔주어야 한다.
데이터프레임을 수정하려면? 먼저 copy()
copy_weather=weather.copy()
copy_weather.columns
Index(['지점', '지점명', '일시', '평균기온(°C)', '최저기온(°C)', '최저기온 시각(hhmi)', '최고기온(°C)',
'최고기온 시각(hhmi)'],
dtype='object')
그리고 일시 컬럼을 datetime으로 먼저 바꿔준 후 분할해야한다.
copy_weather["날짜형식"]=pd.to_datetime(copy_weather["일시"])
copy_weather["년도"]=copy_weather["날짜형식"].dt.year
copy_weather["월"]=copy_weather["날짜형식"].dt.month
copy_weather["일"]=copy_weather["날짜형식"].dt.day
copy_weather.head()

x순서형(시간형) y연속형(숫자형) - lineplot
plt.figure(figsize=(10,5))
plt.title("서울시 2024년도 기온 변화")
# 순서형(시간)과 연속형(숫자)의 시각화는? lineplot
sns.lineplot(data=copy_weather, x="날짜형식", y="평균기온(°C)", errorbar=None, label="평균기온")
sns.lineplot(data=copy_weather, x="날짜형식", y="최저기온(°C)", errorbar=None, label="최저기온")
sns.lineplot(data=copy_weather, x="날짜형식", y="최고기온(°C)", errorbar=None, label="최고기온")
plt.xlabel("일별")
plt.ylabel("기온")
plt.show()

그렇다면 이번달인 3월을 기준으로 작년 3월에는 어땠는지를 알고 싶다면, 다음과 같다.
month_three=copy_weather[copy_weather["월"]==3] # boolean indexing
# month_three=copy_weather.query("월==3") # query()
month_three.shape
(31, 12)
month_three.head()

plt.figure(figsize=(10,3))
plt.title("서울시 2024년도 3월 기온 변화")
sns.lineplot(data=month_three, x="날짜형식", y="평균기온(°C)", errorbar=None, label="평균기온")
sns.lineplot(data=month_three, x="날짜형식", y="최저기온(°C)", errorbar=None, label="최저기온")
sns.lineplot(data=month_three, x="날짜형식", y="최고기온(°C)", errorbar=None, label="최고기온")
plt.xticks(rotation=45) # x축 레이블 회전
plt.xlabel("일")
plt.ylabel("기온")
plt.show()

이렇게 데이터를 시각화하는 다양한 방법들을 보였다.
다시 정리하자면, 단일 변수일때는 범주형과 연속형이 있다.
범주형일때는 빈도수를 나타내는 countplot과 비율을 나타내는 pie chart를 사용하고,
연속형일때는 구간별을 나타내는 histplot과 사분위를 표현해주는 boxplot을 사용한다.
다 변수일때는 크게 세가지로 나누는데,
범주형과 연속형일때는 barplot,
연속형과 연속형일때는 catterplot, lmplot, heatmap,
순서형과 연속형일때는 lineplot을 사용한다.
하지만 가장 중요한 부분은 당연히 연속형과 연속형의 데이터를 시각화할때 상관관계를 나타내는 상관분석에 관한 부분이었다. 앞으로도 계속 나오니 이 부분을 잊지말아야 한다.
지금까지는 seaborn을 중심으로 하고, 옵션에 대한 부분에서 matplotlib을 사용했는데, 앞으로도 쭉 나오니 그럴때마다 내용을 설명하겠다.
다음 글 부터는 확증적 데이터 분석, CDA에 대한 내용을 다루겠다.