You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Data-Science-For-Beginners/translations/ko/1-Introduction/04-stats-and-probability/notebook.ipynb

581 lines
19 KiB

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 확률 및 통계 소개\n",
"이 노트북에서는 이전에 논의했던 몇 가지 개념들을 실습해 보겠습니다. 확률과 통계의 많은 개념들은 Python의 데이터 처리 주요 라이브러리인 `numpy`와 `pandas`에 잘 구현되어 있습니다.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import random\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 확률 변수와 분포\n",
"0부터 9까지의 균등 분포에서 30개의 값을 샘플링해 봅시다. 또한 평균과 분산도 계산할 것입니다.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample = [ random.randint(0,10) for _ in range(30) ]\n",
"print(f\"Sample: {sample}\")\n",
"print(f\"Mean = {np.mean(sample)}\")\n",
"print(f\"Variance = {np.var(sample)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"샘플에 몇 개의 서로 다른 값이 있는지 시각적으로 추정하기 위해, 우리는 **히스토그램**을 그릴 수 있습니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.hist(sample)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 실제 데이터 분석\n",
"\n",
"평균과 분산은 실제 데이터를 분석할 때 매우 중요합니다. [SOCR MLB Height/Weight Data](http://wiki.stat.ucla.edu/socr/index.php/SOCR_Data_MLB_HeightsWeights)에서 야구 선수에 대한 데이터를 불러와 보겠습니다.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df = pd.read_csv(\"../../data/SOCR_MLB.tsv\",sep='\\t', header=None, names=['Name','Team','Role','Weight','Height','Age'])\n",
"df\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> 우리는 데이터 분석을 위해 [**Pandas**](https://pandas.pydata.org/)라는 패키지를 사용하고 있습니다. 이 과정의 후반부에서 Pandas와 파이썬에서 데이터 작업에 대해 더 자세히 다룰 것입니다.\n",
"\n",
"나이, 키, 몸무게의 평균 값을 계산해 보겠습니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[['Age','Height','Weight']].mean()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이제 키에 집중하여 표준 편차와 분산을 계산해 봅시다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(list(df['Height'])[:20])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"mean = df['Height'].mean()\n",
"var = df['Height'].var()\n",
"std = df['Height'].std()\n",
"print(f\"Mean = {mean}\\nVariance = {var}\\nStandard Deviation = {std}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"평균 외에도 중앙값과 사분위수를 살펴보는 것이 의미가 있습니다. 이는 **박스 플롯**을 사용하여 시각화할 수 있습니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(10,2))\n",
"plt.boxplot(df['Height'].ffill(), vert=False, showmeans=True)\n",
"plt.grid(color='gray', linestyle='dotted')\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"데이터셋의 일부 집합, 예를 들어 선수 역할별로 그룹화된 박스 플롯도 만들 수 있습니다.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.boxplot(column='Height', by='Role', figsize=(10,8))\n",
"plt.xticks(rotation='vertical')\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **노트**: 이 다이어그램은 평균적으로 1루수의 키가 2루수의 키보다 더 크다는 것을 시사합니다. 나중에 우리는 이 가설을 더 공식적으로 테스트하는 방법과 데이터가 통계적으로 유의미함을 입증하는 방법을 배울 것입니다. \n",
"\n",
"나이, 키, 체중은 모두 연속형 확률 변수입니다. 이들의 분포는 어떤 형태일까요? 알아내는 좋은 방법은 값들의 히스토그램을 그려보는 것입니다: \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df['Weight'].hist(bins=15, figsize=(10,6))\n",
"plt.suptitle('Weight distribution of MLB Players')\n",
"plt.xlabel('Weight')\n",
"plt.ylabel('Count')\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 정규 분포\n",
"\n",
"실제 데이터와 동일한 평균과 분산을 갖는 정규 분포를 따르는 인공적인 무게 샘플을 만들어 봅시다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"generated = np.random.normal(mean, std, 1000)\n",
"generated[:20]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(10,6))\n",
"plt.hist(generated, bins=15)\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(10,6))\n",
"plt.hist(np.random.normal(0,1,50000), bins=300)\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"대부분의 실제 값들이 정규 분포를 따르기 때문에, 우리는 샘플 데이터를 생성할 때 균등 난수 생성기를 사용해서는 안 됩니다. 다음은 균등 분포( `np.random.rand`로 생성)로 무게를 생성하려고 할 때 발생하는 상황입니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"wrong_sample = np.random.rand(1000)*2*std+mean-std\n",
"plt.figure(figsize=(10,6))\n",
"plt.hist(wrong_sample)\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 신뢰 구간\n",
"\n",
"이제 야구 선수들의 몸무게와 키에 대한 신뢰 구간을 계산해 보겠습니다. 우리는 [이 스택오버플로우 토론](https://stackoverflow.com/questions/15033511/compute-a-confidence-interval-from-sample-data)의 코드를 사용할 것입니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import scipy.stats\n",
"\n",
"def mean_confidence_interval(data, confidence=0.95):\n",
" a = 1.0 * np.array(data)\n",
" n = len(a)\n",
" m, se = np.mean(a), scipy.stats.sem(a)\n",
" h = se * scipy.stats.t.ppf((1 + confidence) / 2., n-1)\n",
" return m, h\n",
"\n",
"for p in [0.85, 0.9, 0.95]:\n",
" m, h = mean_confidence_interval(df['Weight'].fillna(method='pad'),p)\n",
" print(f\"p={p:.2f}, mean = {m:.2f} ± {h:.2f}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 가설 검정\n",
"\n",
"야구 선수 데이터 세트에서 다양한 역할을 살펴봅시다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.groupby('Role').agg({ 'Weight' : 'mean', 'Height' : 'mean', 'Age' : 'count'}).rename(columns={ 'Age' : 'Count'})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"일루미니티스트들을 원래보다 더 높다는 가설을 테스트해 봅시다. 가장 간단한 방법은 신뢰 구간을 테스트하는 것입니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for p in [0.85,0.9,0.95]:\n",
" m1, h1 = mean_confidence_interval(df.loc[df['Role']=='First_Baseman',['Height']],p)\n",
" m2, h2 = mean_confidence_interval(df.loc[df['Role']=='Second_Baseman',['Height']],p)\n",
" print(f'Conf={p:.2f}, 1st basemen height: {m1-h1[0]:.2f}..{m1+h1[0]:.2f}, 2nd basemen height: {m2-h2[0]:.2f}..{m2+h2[0]:.2f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"우리는 구간들이 겹치지 않는다는 것을 볼 수 있습니다.\n",
"\n",
"가설을 증명하는 통계적으로 더 정확한 방법은 **스튜던트 t-검정**을 사용하는 것입니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from scipy.stats import ttest_ind\n",
"\n",
"tval, pval = ttest_ind(df.loc[df['Role']=='First_Baseman',['Height']], df.loc[df['Role']=='Second_Baseman',['Height']],equal_var=False)\n",
"print(f\"T-value = {tval[0]:.2f}\\nP-value: {pval[0]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`ttest_ind` 함수가 반환하는 두 값은 다음과 같습니다:\n",
"* p-값은 두 분포가 같은 평균을 가질 확률로 간주할 수 있습니다. 우리 경우에는 매우 낮아서, 1루수가 더 키가 크다는 강한 증거를 지원한다는 의미입니다.\n",
"* t-값은 t-검정에 사용되는 정규화된 평균 차이의 중간 값이며, 주어진 신뢰 값에 대한 임계값과 비교됩니다.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 중심 극한 정리를 이용한 정규 분포 시뮬레이션\n",
"\n",
"파이썬의 의사 난수 생성기는 균등 분포를 제공하도록 설계되어 있습니다. 만약 정규 분포를 위한 생성기를 만들고 싶다면, 중심 극한 정리를 사용할 수 있습니다. 정규분포 값을 얻기 위해서는 단순히 균등 분포로 생성된 샘플의 평균을 계산하면 됩니다.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def normal_random(sample_size=100):\n",
" sample = [random.uniform(0,1) for _ in range(sample_size) ]\n",
" return sum(sample)/sample_size\n",
"\n",
"sample = [normal_random() for _ in range(100)]\n",
"plt.figure(figsize=(10,6))\n",
"plt.hist(sample)\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 상관관계와 악덕 야구 회사\n",
"\n",
"상관관계는 데이터 시퀀스 간의 관계를 찾을 수 있게 해줍니다. 예제로, 키에 따라 선수들에게 급여를 지급하는 악덕 야구 회사가 있다고 가정해봅시다 - 키가 클수록 더 많은 돈을 받습니다. 기본 급여는 $1000이고, 키에 따라 $0에서 $100 사이의 추가 보너스가 있다고 합시다. MLB의 실제 선수 데이터를 이용해 가상의 급여를 계산해보겠습니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"heights = df['Height'].fillna(method='pad')\n",
"salaries = 1000+(heights-heights.min())/(heights.max()-heights.mean())*100\n",
"print(list(zip(heights, salaries))[:10])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이제 해당 시퀀스들의 공분산과 상관관계를 계산해 보겠습니다. `np.cov`는 소위 **공분산 행렬**을 제공합니다. 이는 공분산을 다변수로 확장한 것입니다. 공분산 행렬 $M$의 원소 $M_{ij}$는 입력 변수 $X_i$와 $X_j$ 간의 공분산이며, 대각선 값 $M_{ii}$는 $X_i$의 분산입니다. 마찬가지로, `np.corrcoef`는 **상관 행렬**을 제공합니다.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(f\"Covariance matrix:\\n{np.cov(heights, salaries)}\")\n",
"print(f\"Covariance = {np.cov(heights, salaries)[0,1]}\")\n",
"print(f\"Correlation = {np.corrcoef(heights, salaries)[0,1]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"상관계수가 1이라는 것은 두 변수 사이에 강한 **선형 관계**가 있음을 의미합니다. 한 값을 다른 값에 대해 플로팅하여 선형 관계를 시각적으로 확인할 수 있습니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(10,6))\n",
"plt.scatter(heights,salaries)\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"관계가 선형이 아닐 때 어떤 일이 일어나는지 봅시다. 우리 회사가 키와 급여 사이의 명백한 선형 의존성을 숨기고, 공식에 `sin`과 같은 비선형성을 도입했다고 가정해봅시다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"salaries = 1000+np.sin((heights-heights.min())/(heights.max()-heights.mean()))*100\n",
"print(f\"Correlation = {np.corrcoef(heights, salaries)[0,1]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이 경우 상관관계는 약간 작지만 여전히 꽤 높습니다. 이제 관계를 덜 명확하게 만들기 위해 급여에 무작위 변수를 추가하여 약간의 추가 무작위성을 더할 수 있습니다. 어떤 일이 일어나는지 봅시다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"salaries = 1000+np.sin((heights-heights.min())/(heights.max()-heights.mean()))*100+np.random.random(size=len(heights))*20-10\n",
"print(f\"Correlation = {np.corrcoef(heights, salaries)[0,1]}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(10,6))\n",
"plt.scatter(heights, salaries)\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> 왜 점들이 이렇게 수직선으로 배열되는지 추측할 수 있나요?\n",
"\n",
"우리는 급여와 같은 인위적으로 만들어진 개념과 관찰된 변수 *키* 사이의 상관관계를 관찰했습니다. 이제 키와 몸무게 같은 두 관찰된 변수도 상관관계가 있는지 살펴보겠습니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"np.corrcoef(df['Height'].ffill(),df['Weight'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"불행하게도, 우리는 아무런 결과도 얻지 못했고, 이상한 `nan` 값들만 보였습니다. 이는 시리즈의 일부 값들이 정의되지 않았고 `nan`으로 표시되어 연산 결과도 정의되지 않기 때문입니다. 행렬을 보면 `Weight`가 문제의 컬럼임을 알 수 있는데, 이는 `Height` 값들 사이의 자기 상관관계가 계산되었기 때문입니다.\n",
"\n",
"> 이 예시는 **데이터 준비**와 **정제**의 중요성을 보여줍니다. 적절한 데이터 없이는 아무것도 계산할 수 없습니다.\n",
"\n",
"`fillna` 메서드를 사용하여 누락된 값을 채우고, 상관관계를 계산해 보겠습니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"np.corrcoef(df['Height'].fillna(method='pad'), df['Weight'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"실제로 상관관계는 존재하지만, 우리의 인위적인 예만큼 강하지는 않습니다. 실제로 한 값을 다른 값에 대해 산점도로 살펴보면, 그 관계는 훨씬 덜 명확할 것입니다:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(10,6))\n",
"plt.scatter(df['Weight'],df['Height'])\n",
"plt.xlabel('Weight')\n",
"plt.ylabel('Height')\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 결론\n",
"\n",
"이 노트북에서는 통계 함수를 계산하기 위해 데이터에 대해 기본 연산을 수행하는 방법을 배웠습니다. 이제 우리는 몇 가지 가설을 증명하기 위해 수학과 통계의 견고한 도구를 사용하는 방법과 데이터 샘플이 주어졌을 때 임의 변수에 대한 신뢰 구간을 계산하는 방법을 알게 되었습니다.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n\n<!-- CO-OP TRANSLATOR DISCLAIMER START -->\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 이용하여 번역되었습니다. 정확성을 위해 노력하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있음을 유의해 주시기 바랍니다. 원본 문서는 해당 언어의 원문이 권위 있는 자료로 간주되어야 합니다. 중요한 정보의 경우, 전문 인간 번역을 권장합니다. 본 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해서는 당사가 책임을 지지 않습니다.\n<!-- CO-OP TRANSLATOR DISCLAIMER END -->\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "86193a1ab0ba47eac1c69c1756090baa3b420b3eea7d4aafab8b85f8b312f0c5"
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
},
"coopTranslator": {
"original_hash": "0f899e3c5019f948e7c787b22f3b2304",
"translation_date": "2026-01-16T11:00:20+00:00",
"source_file": "1-Introduction/04-stats-and-probability/notebook.ipynb",
"language_code": "ko"
}
},
"nbformat": 4,
"nbformat_minor": 4
}