import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from glob import glob

Pandas

판다스는 데이터 조작과 분석을 위한 오픈 라이브러리입니다. 판다스는 표 형태의 데이터 구조인 DataFrame을 제공합니다.

DataFrame

데이터 프레임은 행과 열로 구성된 표 형태로 인덱스와 필드명 로우를 갖고 있습니다.

seaborn

파이썬은 matplotlib에 기반하여 만들어진 고급 그래픽스 패키지입니다.

  1. 데이터 시각화 스타일 설정: Seaborn은 Matplotlib의 기본 스타일보다 더 강력한 기본 스타일을 제공하며, 사용자가 시각화의 전반적인 스타일을 손쉽게 설정할 수 있습니다.

  2. 색상 팔레트: Seaborn은 데이터 포인트, 그래프 요소, 카테고리 등에 대한 색상 팔레트를 지원하여 그래프를 더 다채롭고 시각적으로 풍부하게 만들어 줍니다.

  3. 통계적 그래픽스 기능: Seaborn은 통계적 그래픽스 작업을 위한 다양한 기능을 제공합니다. 이를 통해 회귀 분석, 분포 시각화, 범주형 데이터 분석, 다차원 데이터 시각화 등을 수행할 수 있습니다.

  4. 카테고리 데이터의 시각화: Seaborn은 범주형 데이터를 시각화하는 데 특화된 기능을 제공합니다. 막대 그래프, 바이올린 플롯, 박스 플롯 등을 사용하여 카테고리 데이터의 분포와 관계를 시각적으로 분석할 수 있습니다.

  5. 시계열 데이터 시각화: Seaborn은 시계열 데이터를 시각화하는 데 유용한 기능을 제공합니다. 시계열 그래프, 시계열 분포 플롯, 시계열 히트맵 등을 사용하여 시간에 따른 데이터의 패턴과 트렌드를 시각화할 수 있습니다.

# pandas 의 read로 csv파일 읽기
df = pd.read_csv('csv\\xAPI-Edu-Data.csv')
df.head()
gender NationalITy PlaceofBirth StageID GradeID SectionID Topic Semester Relation raisedhands VisITedResources AnnouncementsView Discussion ParentAnsweringSurvey ParentschoolSatisfaction StudentAbsenceDays Class
0 M KW KuwaIT lowerlevel G-04 A IT F Father 15 16 2 20 Yes Good Under-7 M
1 M KW KuwaIT lowerlevel G-04 A IT F Father 20 20 3 25 Yes Good Under-7 M
2 M KW KuwaIT lowerlevel G-04 A IT F Father 10 7 0 30 No Bad Above-7 L
3 M KW KuwaIT lowerlevel G-04 A IT F Father 30 25 5 35 No Bad Above-7 L
4 M KW KuwaIT lowerlevel G-04 A IT F Father 40 50 12 50 No Bad Above-7 M

분석의 목적이 되는 데이터는 Class

왼쪽의 0,1,2,3,4… 는 index로서 primary key값이다.

만약 위에 필드명 열이 없다면 데이터시리즈이지 데이터프레임이 아니다.

각 파일의 컬럼은 아래와 같습니다.

  • gender: 학생의 성별 (M: 남성, F: 여성)

  • NationaliTy: 학생의 국적

  • PlaceofBirth: 학생이 태어난 국가

  • StageID: 학생이 다니는 학교 (초,중,고)

  • GradeID: 학생이 속한 성적 등급

  • SectionID: 학생이 속한 반 이름

  • Topic: 수강한 과목

  • Semester: 수강한 학기 (1학기/2학기)

  • Relation: 주 보호자와 학생의 관계

  • raisedhands: 학생이 수업 중 손을 든 횟수

  • VisITedResources: 학생이 과목 공지를 확인한 횟수

  • Discussion: 학생이 토론 그룹에 참여한 횟수

  • ParentAnsweringSurvey: 부모가 학교 설문에 참여했는지 여부

  • ParentschoolSatisfaction: 부모가 학교에 만족했는지 여부

  • StudentAbscenceDays: 학생의 결석 횟수 (7회 이상/미만)

  • Class: 학생의 성적 등급 (L: 낮음, M: 보통, H: 높음)

  • announcements View : 공지사항을 확인한 횟수

tmp=df['gender'].value_counts()
tmp
M    305
F    175
Name: gender, dtype: int64

위의 데이터시리즈에선 M,F가 index가 되고 수치가 value가 된다.

plt.bar(tmp.index,tmp)
<BarContainer object of 2 artists>

# 함수화 하여 원하는 필드의 bar차트 그리기

def itemCountChart(field):
    tmp=df[field].value_counts()
    plt.bar(tmp.index,tmp)
    plt.title(field)

itemCountChart('Class')

plt.plot(df['raisedhands'])
plt.plot(df['VisITedResources'])
[<matplotlib.lines.Line2D at 0x21c275d81d0>]

기본적으로 plt.show()를 하지 않으면 그래프가 겹쳐서 나오는 것을 확인할 수 있다.

plt.scatter(df['VisITedResources'] ,df['raisedhands']) # x데이터가 변하면 y데이터도 변할까?
<matplotlib.collections.PathCollection at 0x21c27556d50>

손든 횟수와 공지를 확인한 횟수 간의 연관성이 조금 있다고 볼 수 있다.

seaborn패키지를 활용하여 히스토그램 그리기

sns.histplot(x='raisedhands', data=df, hue='Class', hue_order=['L', 'M', 'H'], kde=True)
# hue : 묶을 데이터
<Axes: xlabel='raisedhands', ylabel='Count'>

학생들의 손든 횟수는 클래스에 영향을 미친다는 것을 알 수 있다.

sns.histplot(x='VisITedResources', data=df, hue='Class', hue_order=['L', 'M', 'H'], kde=True)
<Axes: xlabel='VisITedResources', ylabel='Count'>

공지확인 횟수도 클래스에 영향을 미치므로 x변수로 활용할 수 있다.

sns.jointplot(x='VisITedResources', y='raisedhands', data=df, hue='Class', hue_order=['L', 'M', 'H'])
<seaborn.axisgrid.JointGrid at 0x21c251a7c90>

M라인은 손 든 횟수에서 의미가 없는 분포를 보이지만, L과 H는 확실하게 구분이 된다.

joinplot

jointplot은 Seaborn 라이브러리에서 제공하는 함수로, 두 변수의 관계를 시각화하기 위해 사용되는 그래프입니다. jointplot은 주로 두 변수의 분포와 함께 상관 관계를 확인하는데 사용됩니다.

jointplot은 기본적으로 두 변수의 산점도(scatter plot)를 그려줍니다. 이를 통해 두 변수 간의 상관 관계를 파악할 수 있습니다. 또한, jointplot은 주변에 각 변수의 분포를 나타내는 히스토그램 또는 커널 밀도 추정 그래프를 함께 제공합니다. 이를 통해 변수의 분포와 함께 상관 관계를 시각적으로 확인할 수 있습니다.

jointplot을 사용하여 다음과 같은 정보를 얻을 수 있습니다:

  1. 두 변수의 분포: jointplot은 각 변수의 분포를 히스토그램 또는 커널 밀도 추정 그래프로 보여줍니다. 이를 통해 변수의 분포 모양과 데이터의 밀집도를 확인할 수 있습니다.

  2. 산점도: jointplot은 두 변수의 관계를 산점도로 시각화합니다. 데이터 포인트의 분포와 패턴을 확인하여 두 변수 간의 상관 관계를 파악할 수 있습니다.

  3. 상관 관계: jointplot은 상관 계수를 함께 제공하여 두 변수 간의 선형 상관 관계의 강도를 평가할 수 있습니다. 상관 계수는 -1에서 1 사이의 값으로 표현되며, 양의 값은 양의 상관 관계를, 음의 값은 음의 상관 관계를 나타냅니다.

jointplot은 데이터 분석과 시각화에서 두 변수 간의 관계를 살펴보는 데 유용한 도구입니다. 분포, 상관 관계, 이상치 등을 한눈에 파악할 수 있어 데이터 분석 작업에서 널리 활용되고 있습니다.

sns.pairplot(df, hue='Class', hue_order=['L', 'M', 'H'])
<seaborn.axisgrid.PairGrid at 0x21c27788510>

그래프만 보고 판단할 때, 손든 횟수와 공지사항 본 횟수가 우상향(/)인 것을 확인할 수 있다.

# seaborn의 countplot()을 사용
# Hint) x와 hue를 사용하여 범주별 Class 통계 확인

sns.countplot(x='Class', data=df, order=['L', 'M', 'H'])
<Axes: xlabel='Class', ylabel='count'>

countplot은 Seaborn 라이브러리에서 제공하는 함수로, 카테고리형 변수의 빈도를 시각화하는데 사용됩니다. countplot은 각 카테고리 값이 데이터셋에서 등장하는 빈도를 막대 그래프로 나타냅니다.

데이콘-KBO OPS예측 데이터

야구 데이터로 불확실성 문제를 해결하기 위해 2019년 타자들의 상반기 성적 예측을 목표로 합니다.

  • 파일 목록
  1. Regular_Season_Batter.csv : KBO에서 활약한 타자들의 역대 정규시즌 성적을 포함하여 몸무게, 키 ,생년월일 등의 기본정보

  2. Regular_Season_Batter_Day_by_Day.csv: KBO에서 활약한 타자들의 일자 별 정규시즌 성적

  3. Pre_Season_Batter.csv : KBO에서 활약한 타자들의 역대 시범경기(정규시즌 직전에 여는 연습경기) 성적

  4. submission.csv : 참가자들이 예측해야 할 타자의 이름과 아이디 목록

fileList=glob('csv\\kbo\\*.csv')
fileList
['csv\\kbo\\Pre_Season_Batter.csv',
 'csv\\kbo\\Regular_Season_Batter.csv',
 'csv\\kbo\\Regular_Season_Batter_Day_by_Day_b4.csv',
 'csv\\kbo\\submission.csv']
# 프리시즌 데이터프레임
preSeason_df = pd.read_csv(fileList[0])
# 정규시즌 데이터프레임
reSeasson_df = pd.read_csv(fileList[1])

# 컬럼들이 일치하는지 확인하기
cnt_cols = preSeason_df.columns == reSeasson_df.columns
cnt_cols[cnt_cols==False]
array([], dtype=bool)
# True에 해당하는 컬럼의 개수를 return한다.
result = np.unique(cnt_cols,return_counts=True)
result
(array([ True]), array([29], dtype=int64))
# plt.bar(x=result[0], y=result[1])

type(result[0]),type(result[1]),type(result[0][0]),type(result[1][0])
(numpy.ndarray, numpy.ndarray, numpy.bool_, numpy.int64)

위의 코드는 에러가 TypeError: bar() missing 1 required positional argument: 'height' 에러가 나오게 된다.

49번라인을 보면 result tuple 구조에서 두번째 값이 array()구조로 되어 있기 떄문이다.

라고 강사님이 말했지만 yheight로 바꾸면 잘만 나온다

plt.bar(x=result[0][0], height=result[1])
<BarContainer object of 1 artists>

point. 프리시즌의 성적이 정규시즌에 영향을 미칠것인가?

참고링크

preSeason_df.head
<bound method NDFrame.head of       batter_id batter_name  year team    avg   G  AB  R   H  2B  ...  GDP  \
0             0        가르시아  2018   LG  0.350   7  20  1   7   1  ...    1   
1             1         강경학  2011   한화  0.000   4   2  2   0   0  ...    0   
2             1         강경학  2014   한화      -   4   0  2   0   0  ...    0   
3             1         강경학  2015   한화  0.130  10  23  3   3   0  ...    0   
4             1         강경학  2016   한화  0.188  14  32  4   6   1  ...    0   
...         ...         ...   ...  ...    ...  ..  .. ..  ..  ..  ...  ...   
1388        342         황재균  2014   롯데  0.407  10  27  3  11   2  ...    0   
1389        342         황재균  2015   롯데  0.333  11  30  8  10   3  ...    0   
1390        342         황재균  2016   롯데  0.310  16  42  8  13   3  ...    0   
1391        342         황재균  2018   KT  0.250   6  16  3   4   1  ...    0   
1392        344         황진수  2014   롯데  0.000   1   1  1   0   0  ...    0   

        SLG    OBP  E  height/weight      year_born   position  \
0     0.550  0.409  1     177cm/93kg  1985년 04월 12일  내야수(우투우타)   
1     0.000  0.500  0     180cm/72kg  1992년 08월 11일  내야수(우투좌타)   
2       NaN    NaN  0     180cm/72kg  1992년 08월 11일  내야수(우투좌타)   
3     0.130  0.286  2     180cm/72kg  1992년 08월 11일  내야수(우투좌타)   
4     0.281  0.212  0     180cm/72kg  1992년 08월 11일  내야수(우투좌타)   
...     ...    ... ..            ...            ...        ...   
1388  0.593  0.448  1     183cm/96kg  1987년 07월 28일  내야수(우투우타)   
1389  0.433  0.389  0     183cm/96kg  1987년 07월 28일  내야수(우투우타)   
1390  0.429  0.370  1     183cm/96kg  1987년 07월 28일  내야수(우투우타)   
1391  0.500  0.333  3     183cm/96kg  1987년 07월 28일  내야수(우투우타)   
1392  0.000  0.000  0     181cm/82kg  1989년 02월 15일  내야수(우투양타)   

                                      career  starting_salary    OPS  
0     쿠바 Ciego de Avila Maximo Gomez Baez(대)              NaN  0.959  
1                          광주대성초-광주동성중-광주동성고          10000만원  0.500  
2                          광주대성초-광주동성중-광주동성고          10000만원    NaN  
3                          광주대성초-광주동성중-광주동성고          10000만원  0.416  
4                          광주대성초-광주동성중-광주동성고          10000만원  0.493  
...                                      ...              ...    ...  
1388     사당초-이수중-경기고-현대-우리-히어로즈-넥센-롯데-샌프란시스코           6000만원  1.041  
1389     사당초-이수중-경기고-현대-우리-히어로즈-넥센-롯데-샌프란시스코           6000만원  0.822  
1390     사당초-이수중-경기고-현대-우리-히어로즈-넥센-롯데-샌프란시스코           6000만원  0.799  
1391     사당초-이수중-경기고-현대-우리-히어로즈-넥센-롯데-샌프란시스코           6000만원  0.833  
1392                             석천초-대헌중-공주고           4000만원  0.000  

[1393 rows x 29 columns]>
# 데이터 시각화
preSeason_df.hist(figsize=(10,9))
plt.tight_layout() # 그래프 간격 설정
plt.show()

프리시즌의 컬럼별 히스토그램을 보면 어떤 데이터를 어디까지 사용해야할지 대강 판별할 수 있다.

# 데이터 기초통계량 확인
display(preSeason_df.describe())
batter_id year G AB R H 2B 3B HR TB ... SB CS BB HBP SO GDP SLG OBP E OPS
count 1393.000000 1393.000000 1393.000000 1393.000000 1393.000000 1393.000000 1393.000000 1393.000000 1393.000000 1393.000000 ... 1393.000000 1393.000000 1393.000000 1393.000000 1393.000000 1393.000000 1364.000000 1368.000000 1393.000000 1364.000000
mean 173.434314 2013.014358 8.705671 19.201723 2.679828 5.021536 0.954774 0.119885 0.391960 7.391960 ... 0.629576 0.291457 1.877961 0.330223 3.714286 0.447236 0.361012 0.317912 0.381910 0.676924
std 94.716851 4.166757 5.562686 13.395946 2.637212 4.232584 1.196904 0.379976 0.748557 6.538787 ... 1.146854 0.595522 2.053392 0.642204 3.180884 0.723364 0.269892 0.151489 0.729521 0.386933
min 0.000000 2002.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
25% 99.000000 2010.000000 6.000000 9.000000 1.000000 2.000000 0.000000 0.000000 0.000000 2.000000 ... 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.217000 0.250000 0.000000 0.472000
50% 178.000000 2014.000000 9.000000 18.000000 2.000000 4.000000 1.000000 0.000000 0.000000 6.000000 ... 0.000000 0.000000 1.000000 0.000000 3.000000 0.000000 0.344500 0.333000 0.000000 0.675000
75% 254.000000 2017.000000 11.000000 28.000000 4.000000 8.000000 2.000000 0.000000 1.000000 11.000000 ... 1.000000 0.000000 3.000000 1.000000 5.000000 1.000000 0.478000 0.400000 1.000000 0.867000
max 344.000000 2018.000000 119.000000 183.000000 35.000000 51.000000 11.000000 4.000000 5.000000 68.000000 ... 9.000000 4.000000 21.000000 4.000000 36.000000 5.000000 4.000000 1.000000 5.000000 5.000000

8 rows × 21 columns

# 정규시즌 데이터에서 연도별 기록된 선수의 수
regular_count = reSeasson_df.groupby('year')['batter_id'].count().rename('regular')

# 프리시즌 데이터에서 연도별 기록된 선수의 수
pre_count = preSeason_df.groupby('year')['batter_id'].count().rename('preseason')

pd.concat([regular_count, pre_count, np.round(pre_count/regular_count,2).rename('ratio')],
            axis = 1).transpose().loc[:,2002:] # 2002년 이후 부터 보기
year 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018
regular 43.00 54.00 68.00 73.00 85.00 98.00 115.00 124.00 130.00 151.0 174.0 194.00 186.00 207.00 213.00 217.00 227.0
preseason 12.00 19.00 28.00 37.00 36.00 43.00 61.00 66.00 72.00 75.0 87.0 104.00 117.00 134.00 153.00 167.00 182.0
ratio 0.28 0.35 0.41 0.51 0.42 0.44 0.53 0.53 0.55 0.5 0.5 0.54 0.63 0.65 0.72 0.77 0.8

정규시즌데이터와 프리시즌 데이터를 year별로 타자의 고유아이디의 개수를 샌 새로운 컬럼을 지정해주고,

판다스의 concat을 이용하여 3개의 컬럼을 보여주고 있다.

np.round는 각 시즌, 연도별 타자의 수를 간단한 비율로 나타내어 반올림한 정수이다.

transpose는 배열의 차원을 전환하는 함수입니다. 배열의 행과 열을 서로 바꾸어 새로운 배열을 생성합니다.

아래의 코드를 참고하면 regular_count가 어떠한 데이터로 나오는지 확인할 수 있습니다.

비교를 편하게 하기 위해선 각 연도를 컬럼명으로 나오게 데이터프레임 설정을 하기 위하여 사용합니다.

loc는 행과 열을 라벨을 기반으로 선택하는 인덱싱 방법입니다. 데이터프레임에서 원하는 데이터를 라벨을 사용하여 추출하거나 조작하는 데에 사용됩니다.

프리시즌의 기록된 선수가 현저히 적으므로 정규시즌데이터와 비교하기엔 무리가 있다는 것을 볼 수 있다.

display(regular_count)
year
1993      1
1994      2
1995      1
1996      7
1997      8
1998     10
1999     14
2000     20
2001     32
2002     43
2003     54
2004     68
2005     73
2006     85
2007     98
2008    115
2009    124
2010    130
2011    151
2012    174
2013    194
2014    186
2015    207
2016    213
2017    217
2018    227
Name: regular, dtype: int64
# 타자의 이름과 연도를 이용해 새로운 인덱스를 생성 (가르시아2018) 식으로 생성됌
reSeasson_df['new_idx'] =    reSeasson_df['batter_name'] + \
                            reSeasson_df['year'].apply(str) # java와 달리 개행해서 코딩하려면 이렇게 개행해야함
preSeason_df['new_idx'] =   preSeason_df['batter_name'] + preSeason_df['year'].apply(str)

display(reSeasson_df['new_idx'])
0       가르시아2018
1        강경학2011
2        강경학2014
3        강경학2015
4        강경학2016
          ...   
2449     황진수2014
2450     황진수2015
2451     황진수2016
2452     황진수2017
2453     황진수2018
Name: new_idx, Length: 2454, dtype: object

프리시즌 데이터와 정규시즌 데이터의 기록 수가 다르므로 타자+연도 데이터로 연도별 두 시즌 모두 기록된 타자들만 뽑아내기 위함

# 새로운 인덱스의 교집합
intersection_idx = list(set(reSeasson_df['new_idx']).\
                            intersection(preSeason_df['new_idx']))

# 교집합에 존재하는 데이터만 불러오기
reSeasson_new = reSeasson_df.loc[reSeasson_df['new_idx'].\
                                apply(lambda x: x in intersection_idx)].copy()
reSeasson_new = reSeasson_new.sort_values(by='new_idx').reset_index(drop=True)
preSeason_new = preSeason_df.loc[preSeason_df['new_idx'].\
                                apply(lambda x:x in intersection_idx)].copy()
preSeason_new = preSeason_new.sort_values(by='new_idx').reset_index(drop=True)



reSeasson_new.head(3)
batter_id batter_name year team avg G AB R H 2B ... SLG OBP E height/weight year_born position career starting_salary OPS new_idx
0 0 가르시아 2018 LG 0.339 50 183 27 62 9 ... 0.519 0.383 9 177cm/93kg 1985년 04월 12일 내야수(우투우타) 쿠바 Ciego de Avila Maximo Gomez Baez(대) NaN 0.902 가르시아2018
1 1 강경학 2011 한화 0.000 2 1 0 0 0 ... 0.000 0.000 1 180cm/72kg 1992년 08월 11일 내야수(우투좌타) 광주대성초-광주동성중-광주동성고 10000만원 0.000 강경학2011
2 1 강경학 2014 한화 0.221 41 86 11 19 2 ... 0.349 0.337 6 180cm/72kg 1992년 08월 11일 내야수(우투좌타) 광주대성초-광주동성중-광주동성고 10000만원 0.686 강경학2014

3 rows × 30 columns

intersection()는 Set에서 사용되는 함수로 두집합의 교집합을 반환합니다.

copy()는 Python에서 객체를 복사하는 함수입니다. 이 함수를 사용하여 원본 객체의 복사본을 생성할 수 있습니다. 복사된 객체는 원본과 독립적으로 존재하며, 값을 변경해도 원본 객체에 영향을 주지 않습니다.

람다함수를 이용하여 reSeasson_new

비교를 위하여 sort_valuesnew_idx로 정렬합니다.

reset_index는 인덱스를 재설정하는 함수입니다. 데이터프레임에 새로운 인덱스를 할당했으므로 인덱스가 변경됩니다.

# 교집합 검증
print(reSeasson_new.shape, preSeason_new.shape)
sum(reSeasson_new['new_idx'] == preSeason_new['new_idx'])
(1358, 30) (1358, 30)
1358
from matplotlib import font_manager, rc
import platform
import matplotlib

# 한글폰트
if platform.system() == 'Windows':
# 윈도우인 경우 맑은 고딕 폰트 이용
    font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf"
                                           ).get_name()
    rc('font', family=font_name)
else:    
# Mac 인 경우
    rc('font', family='AppleGothic')

#그래프에서 마이너스 기호가 표시되게 하는 설정입니다.
matplotlib.rcParams['axes.unicode_minus'] = False

# 정규시즌과 프리시즌의 상관관계 계산
correlation = reSeasson_new['OPS'].corr(preSeason_new['OPS'])
sns.scatterplot(x=reSeasson_new['OPS'], y=preSeason_new['OPS'])
plt.title('correlation(상관계수): ' + str(np.round(correlation,2)), fontsize=20)
plt.xlabel("정규시즌 OPS",fontsize=12)
plt.ylabel("프리시즌 OPS",fontsize=12)
plt.show()

corr()는 로우간의 상관 관계를 계산하는 함수

상관계수는 -1부터 1까지의 범위갖기 때문에 두 변수 간의 선형적인 관계를 나타냅니다.

  • chat gpt 설명 참고

    • 상관 계수의 절댓값이 1에 가까울수록 두 변수 간의 선형 관계가 강하다는 것을 의미합니다. 절댓값이 1에 가까워질수록 두 변수의 관계가 더 강력하고 예측 가능합니다. 반대로, 상관 계수가 0에 가까울수록 두 변수 간의 선형 관계는 약해지며, 상관성이 적다고 판단할 수 있습니다.

상관계수가 0.7 이상이되어야 의미가 있으나, 프리시즌으로는 정규시즌과 비교할수없다.

그러므로 정규시즌 데이터만으로 분석한다.

댓글남기기