Skip to main content

04. 판다스 데이터프레임 기초 이해

About 7 minPythoncrashcoursepythonpygooglegoogle-colabjupyter-notebooknumpypandasipython

04. 판다스 데이터프레임 기초 이해 관련


04. 판다스 데이터프레임 기초 이해

금융 데이터 분석을 위한 파이썬 - WikiDocs

01. 데이터프레임의 출력

판다스는 데이터가 위치한 URL을 입력하는 것만으로도 데이터를 바로 읽어오는 기능을 가지고 있습니다. csv 파일 형태의 데이터를 로드하여 데이터프레임에 저장해봅시다. csv 파일을 데이터프레임으로 읽을 때 pd.read_csv()를 사용합니다. 데이터가 위치한 url을 기재하여 파일을 읽어옵시다.

데이터 로드

import pandas as pd

url = 'https://raw.githubusercontent.com/justmarkham/DAT8/master/data/drinks.csv'
drink_df = pd.read_csv(url, ',')

drink_df의 타입을 확인해봅시다.

# 타입 확인
type(drink_df)

데이터프레임이라고 출력됩니다. drink_df를 출력해봅시다.

drink_df
drink_df
drink_df

데이터프레임의 출력

head()는 상위 5개의 행을 출력합니다.

상위 5개
# 상위 5개의 행을 출력
drink_df.head()
head5
head5

데이터프레임의 인덱스

index는 데이터프레임의 인덱스를 확인합니다.

# 인덱스의 범위 확인
drink_df.index
# 
# RangeIndex(start=0, stop=193, step=1)

현재 인덱스는 0부터 시작해서 +1씩(step) 증가하여 192까지 있습니다. 참고로 193에서 stop한다는 것은 193은 포함하지 않는다는 의미입니다. 즉, 0번부터 192번까지의 샘플이 있는 셈이므로 총 샘플의 수는 193개입니다.

데이터프레임의 데이터타입

dtypes는 각 데이터프레임의 열의 타입을 확인할 수 있습니다.

# 각 컬럼의 타입 출력
drink_df.dtypes
# 
# country                          object
# beer_servings                     int64
# spirit_servings                   int64
# wine_servings                     int64
# total_litres_of_pure_alcohol    float64
# continent                        object
# dtype: object

참고로 데이터프레임의 타입에서 object라고 표현되는 부분은 해당 타입이 문자열이라는 의미입니다. int64는 정수형 데이터, float64는 실수형 데이터를 의미합니다.

다시 말해 위의 출력 결과를 해석해보면, country, continent 열은 문자열 데이터로 구성되어져 있고, beer_servings, spirit_servings, wine_servings 열은 정수형 데이터, total_litres_of_prue_alcohol 열은 실수형 데이터로 구성되어져 있다는 의미입니다. 이를 통해서 각 열의 데이터 자료형이 무엇인지를 파악할 수 있습니다. shape는 데이터프레임의 행과 열의 수를 확인할 수 있습니다.

데이터프레임의 크기

# 데이터프레임의 행과 열의 개수 출력
drink_df.shape

위 출력 결과는 해당 데이터프레임이 193개의 행, 6개의 열을 가지고 있음을 알려줍니다.

데이터프레임의 행렬 변환

values는 데이터프레임 형태를 Numpy 행렬 형태로 변환하여 출력합니다.

# Numpy 타입으로 출력
drink_df.values
#
# array([['Afghanistan', 0, 0, 0, 0.0, 'AS'],
#        ['Albania', 89, 132, 54, 4.9, 'EU'],
#        ['Algeria', 25, 0, 14, 0.7, 'AF'],
#        ...,
#        ['Yemen', 6, 0, 0, 0.1, 'AS'],
#        ['Zambia', 32, 19, 4, 2.5, 'AF'],
#        ['Zimbabwe', 64, 18, 4, 4.7, 'AF']], dtype=object)
type(drink_df.values)
#
# numpy.ndarray

Numpy에 대해서는 이 수업에서 다루지는 않았지만 정말~ 간단히 요약하자면 각각의 행을 아래와 같이 데이터프레임의 행 형태에서 아래와 같은 형태로 변환된다고 보면 되겠습니다.

  • ['Afghanistan', 0, 0, 0, 0.0, 'AS']

변환한 데이터의 첫번째 행을 출력해볼까요? 이는 변환 후 0번 인덱스로 접근하면 됩니다.

drink_df.values[0]
#
# array(['Afghanistan', 0, 0, 0, 0.0, 'AS'], dtype=object)

위와 같은 형태로 변환이 되었다는 것은 다음과 같이 for문 접근도 가능하다는 의미입니다.

for element in drink_df.values[0]:
  print(element)
#   
# Afghanistan
# 0
# 0
# 0
# 0.0
# AS

데이터프레임의 info

info()는 데이터프레임의 전반적인 정보를 보여줍니다. info() 사용하고, info()를 통해 알 수 있는 정보들을 최대한 정리해봅시다.

drink_df.info()
#
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 193 entries, 0 to 192
# Data columns (total 6 columns):
#  #   Column                        Non-Null Count  Dtype  
# ---  ------                        --------------  -----  
#  0   country                       193 non-null    object 
#  1   beer_servings                 193 non-null    int64  
#  2   spirit_servings               193 non-null    int64  
#  3   wine_servings                 193 non-null    int64  
#  4   total_litres_of_pure_alcohol  193 non-null    float64
#  5   continent                     170 non-null    object 
# dtypes: float64(1), int64(3), object(2)
# memory usage: 9.2+ KB

info()를 통해 다음과 같은 정보를 알 수 있습니다.

해당 데이터는 다음과 같은 열들을 가지고 있습니다. 데이터 분석 용어로는 데이터를 파악하기 위한 이러한 열을 특성(feature) 이라고 부릅니다. 총 6개의 특성이 있는 셈입니다.

  • country : 국가
  • beer_servings : 맥주 소비량
  • spiti_servings : spirit 소비량
  • wine_servings : wine 소비량
  • total_litres_of_pure_alcohol : 총 알코올 소비량
  • continent : 대륙 정보

info()의 출력 결과로부터 총 193개의 데이터가 존재하며, contry, continet 데이터의 경우에는 object. 데이터프레임에서 object는 문자열을 의미합니다. 그 외에는 정수형 데이터(int64) 또는 실수형 데이터(float64)로 구성되어져 있음을 알 수 있습니다.

또 하나 주목할 점은 데이터의 총 개수는 193개인데, Non-Null Count를 보면 continent의 경우에만 170개가 있습니다.

이는 Null 데이터. 다시 말해 결측 데이터가 23개 존재한다는 것을 의미합니다.

데이터프레임의 결측값(Null)

결측값이란 정상적으로 존재하지 않는 경우의 값을 의미합니다. Null 값이라고도 표현합니다. isnull().sum()은 해당 데이터프레임의 각 열에서 Null 데이터가 총 몇 개인지를 출력합니다.

print(drink_df.isnull().sum())
# 
# country                          0
# beer_servings                    0
# spirit_servings                  0
# wine_servings                    0
# total_litres_of_pure_alcohol     0
# continent                       23
# dtype: int64

continent라는 열에서 총 23개의 Null`(결측) 데이터가 있음을 확인할 수 있습니다.


02. 데이터프레임의 열 접근

앞서 사용했던 동일한 데이터프레임인 drink_df를 사용합니다.

데이터 로드

import pandas as pd

url = 'https://raw.githubusercontent.com/justmarkham/DAT8/master/data/drinks.csv'
drink_df = pd.read_csv(url, ',')
drink_df
drink_df
drink_df

데이터프레임의 열 접근

데이터프레임의 특정 열에 접근하는 가장 쉬운 방법은

데이터프레임의 이름.열의 이름

과 같은 방식으로 접근하는 것입니다. 다시 말해, 데이터프레임의 이름을 적고, 온점을 찍은 후에 열의 이름을 적으면 해당 열만을 불러옵니다.

drink_df.beer_servings
#
# 0        0
# 1       89
# 2       25
# 3      245
# 4      217
#       ... 
# 188    333
# 189    111
# 190      6
# 191     32
# 192     64
# Name: beer_servings, Length: 193, dtype: int64

또 다른 방법은 데이터프레임의 이름['해당 열의 이름']과 같은 방법입니다. 이는 위와 동일한 결과를 출력합니다.

# 또 다른 방법
drink_df['beer_servings']
#
# 0        0
# 1       89
# 2       25
# 3      245
# 4      217
#       ... 
# 188    333
# 189    111
# 190      6
# 191     32
# 192     64
# Name: beer_servings, Length: 193, dtype: int64

2차원 테이블 형태를 데이터프레임이라고 부르지만, 이렇게 특정 하나의 열만을 불러올 경우에는 데이터프레임이 아니라 데이터 타입이 판다스(Pandas)에서 제공하는 또 다른 데이터 타입인 '시리즈(Series)'가 됩니다. 실제로 특정 열만 불러오고 타입을 확인해볼까요?

# 컬럼의 타입 확인
type(drink_df.beer_servings)
# 
# pandas.core.series.Series

만약 하나의 열이 아니라 선택적으로 다수의 열에 접근려면 어떻게 하면 될까요?

데이터프레임의 이름[['특정열의 이름1', '특정열의 이름2']]

와 같은 방식으로 접근하면 됩니다. 예를 들어서 beer_servingswine_servings 2개의 열만을 불러오고 싶다고 해봅시다.

drink_df[['beer_servings','wine_servings']] 
drink_df_columns
drink_df_columns

두 개의 열만이 뽑힌 데이터프레임이 출력됩니다.

['특정 열의 이름1', '특정 열의 이름2'] 은 파이썬 리스트 형태입니다.

다시 말해 파이썬 리스트의 형태로 열의 이름을 나열한 뒤에

데이터프레임의 이름[열의 이름이 나열된 파이썬 리스트]

를 사용해도 동일한 결과를 출력합니다.

cols = ['beer_servings','wine_servings']
drink_df[cols]
drink_df_columns
drink_df_columns

03. 특성의 수치 정보 파악하기

앞서 사용했던 동일한 데이터프레임인 drink_df를 사용합니다.

데이터 로드

import pandas as pd

url = 'https://raw.githubusercontent.com/justmarkham/DAT8/master/data/drinks.csv'
drink_df = pd.read_csv(url, ',')
drink_df
drink_df
drink_df

특성(features)

앞서 데이터프레임의 info()를 설명한 적이 있습니다. info()는 데이터프레임의 전반적인 정보를 보여줍니다.

drink_df.info()
# 
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 193 entries, 0 to 192
# Data columns (total 6 columns):
#  #   Column                        Non-Null Count  Dtype  
# ---  ------                        --------------  -----  
#  0   country                       193 non-null    object 
#  1   beer_servings                 193 non-null    int64  
#  2   spirit_servings               193 non-null    int64  
#  3   wine_servings                 193 non-null    int64  
#  4   total_litres_of_pure_alcohol  193 non-null    float64
#  5   continent                     170 non-null    object 
# dtypes: float64(1), int64(3), object(2)
# memory usage: 9.2+ KB

info()를 통해 다음과 같은 정보를 알 수 있습니다.

해당 데이터는 다음과 같은 열들을 가지고 있습니다. 데이터 분석 용어로는 데이터를 파악하기 위한 이러한 열을 특성(feature) 이라고 부릅니다. 총 6개의 특성이 있는 셈입니다.

특성의 수치 정보 파악하기

이제 본격적으로 데이터프레임을 통해서 데이터를 파악해가는 과정을 시작해볼텐데요. 숫자와 같은 수치 데이터를 다루고 있다면, 해당 데이터의 최솟값, 최댓값, 평균값 등을 파악하는 것은 데이터 파악의 가장 첫 걸음입니다.

데이터가 데이터프레임 형태로 저장된 상황에서 이를 가장 빠르게 파악할 수 있는 방법은

describe()를 사용하는 것입니다.

drink_df.describe()
drink_df_describe
drink_df_describe

describe()는 데이터프레임의 총 데이터의 수(count), 평균(mean), 표준편차(std), 분위수(25%, 50%, 75%)를 파악하여 출력합니다. 하지만 이는 숫자. 즉, 수치 정보에 국한되어서 계산하므로 문자열 타입의 데이터였던 country 열과 continent 열은 제외되었습니다. 특정 열에 대해서만 출력해볼 수도 있습니다.

drink_df.beer_servings.describe()
#
# count    193.000000
# mean     106.160622
# std      101.143103
# min        0.000000
# 25%       20.000000
# 50%       76.000000
# 75%      188.000000
# max      376.000000
# Name: beer_servings, dtype: float64

특정 열의 최대값, 최소값, 평균값, 합계, 카운트도 계산가능합니다.

평균
drink_df.beer_servings.mean()
#
# 106.16062176165804

이렇게 바로 계산한 수치를 뽑을 수 있다면, 이 수치를 가지고 계산도 가능하겠죠? mean()으로 평균을 바로 구할 수 있긴 하지만, sum()count()를 이용하여 평균을 계산해봅시다.

float(drink_df.beer_servings.sum())/drink_df.beer_servings.count()
#
# 106.16062176165804

04. 조건부 로직

앞서 사용했던 동일한 데이터프레임인 drink_df를 사용합니다.

데이터 로드

import pandas as pd

url = 'https://raw.githubusercontent.com/justmarkham/DAT8/master/data/drinks.csv'
drink_df = pd.read_csv(url, ',')
drink_df
drink_df
drink_df

조건부 로직

데이터프레임에 우리가 원하는 조건을 걸어서 해당 조건을 충족하는 값들만을 뽑아오는 것도 가능합니다. 우선 특정 열에 대해서 조건을 걸었을 때 어떤 값을 반환하는지를 봅시다.

데이터프레임의 이름.특정 열의 이름 == '특정값'

이라는 코드는 각 행에서 해당 조건을 만족하는지를 판단하여 만족한다면 True, 아니라면 False의 값을 가지는 시리즈(Series)를 리턴합니다.

drink_df.continent=='EU'
#
# 0      False
# 1       True
# 2      False# 3       True
# 4      False
#        ...  
# 188    False
# 189    False
# 190    False
# 191    False
# 192    False
# Name: continent, Length: 193, dtype: bool

하지만 일반적으로 저런 조건을 걸었을 때 우리가 원하는 건 TrueFalse로 구성된 시리즈(Series)가 아니라 continent의 값이 'EU'일 때의 데이터프레임 값을 뽑아내는 것일텐데요.

이 경우에는 저렇게 TrueFalse로 구성된 Series를 다음과 같이 사용해주면 됩니다.

데이터프레임의 이름[TrueFalse로 구성된 시리즈]

일단 drink_df.continent=='EU'의 타입이 시리즈임을 확인합니다.

type(drink_df.continent=='EU')
#
# pandas.core.series.Series

이를 데이터프레임의 이름[ ] 의 대괄호 안에 넣어봅시다. head(20)을 이용하여 상위 20개만 출력합니다.

drink_df[drink_df.continent=='EU'].head(20)
dataframe
dataframe

그렇다면 만약 beer_servings의 값이 158보다 큰 경우만을 필터링하고 싶다면 어떨까요?

앞서 데이터프레임의 열을 호출하는 방법으로 다음과 같이 두 가지를 소개했습니다.

  • 데이터프레임의 이름.열의 이름
  • 데이터프레임의 이름['열의 이름']

다시 말해 beer_servings의 값이 158보다 큰 경우로 필터링하는 코드는 아래의 두 가지 방법이 있습니다.

drink_df[drink_df.beer_servings > 158]
drink_df[drink_df['beer_servings'] > 158]
# 아래 두 가지는 동일함
# drink_df[drink_df.beer_servings > 158]
# drink_df[drink_df['beer_servings'] > 158] 
drink_df[drink_df['beer_servings'] > 158].head(20)
데이터프레임
데이터프레임

그렇다면 조건을 걸되, 특정 열 몇 개만 출력하고 싶다면 어떨까요?

앞서 특정 열들만을 뽑아서 출력하는 방법을 아래와 같이 소개했었습니다.

  • 데이터프레임의 이름[['특정 열의 이름1, '특정 열의 이름2']]

이를 응용하여 beer_servings의 값이 10 이하이면서 country, beer_servings의 두 개의 열만을 뽑아내는 방법은 다음과 같습니다.

drink_df[drink_df.beer_servings <= 10][['country','beer_servings']]

우선, drink_df[drink_df.beer_servings <= 10]로 조건에 맞는 데이터프레임을 뽑아낸 뒤에 이 데이터프레임에 [['country','beer_servings']]를 붙여서 2개의 열만을 뽑아내는 것이죠.

# drink_df[drinks.beer_servings <= 10]는 데이터프레임이다. 
drink_df[drink_df.beer_servings <= 10][['country','beer_servings']].head(20)
데이터프레임2
데이터프레임2

이번에는 조건으로 필터링하여 시리즈(Series)를 얻어낸 뒤에 평균(mean)을 얻어보겠습니다.

drink_df[drink_df.continent=='EU'].beer_servings.mean()
#
# 193.77777777777777

위 코드는 continent의 값이 'EU'인 경우로 데이터프레임을 뽑아내지만, 그 중 beer_servings라는 하나의 열만을 뽑아내므로 (앞서 열을 하나만 뽑을 경우 데이터프레임이 아니라 시리즈가 된다고 언급한 바 있습니다.) 시리즈로 변환되며, 시리즈에 대해서 mean()을 사용하여 평균값을 뽑아내게 됩니다. 결과적으로 continent의 값이 EU인 beer_servings 열의 평균값(mean)을 구하게 되는 것이죠.

이번에는 beer_servings 열의 평균값을 조건에 넣어보겠습니다.

drink_df[drink_df.beer_servings > drink_df.beer_servings.mean()]
데이터프레임3
데이터프레임3

해당 열에 null 값(결측값)이 존재하는지 유무에 대한 True와 False의 시리즈(Series)로 얻어내는 방법은 해당 열의 isnull()을 하면 알 수 있습니다. isnull()은 해당 행에 Null 값이 들어있는 경우만 True를 리턴하고, 아니라면 False를 리턴합니다.

drink_df.continent.isnull()
#
# 0      False
# 1      False
# 2      False
# 3      False
# 4      False
#        ...  
# 188    False
# 189    False
# 190    False
# 191    False
# 192    False
# Name: continent, Length: 193, dtype: bool
#
drink_df.continent.isnull().sum()
#
# 23

이를 이용하면 데이터프레임에서 특정 열에 Null 값이 위치한 경우만 뽑을 수 있습니다.

drink_df[drink_df.continent.isnull()]
데이터프레임4
데이터프레임4
len(drink_df[drink_df.continent.isnull()])
#
# 23

continent의 값에 NaN 값(결측값을 의미)인 경우만 출력되는 것을 확인할 수 있습니다. 즉, 이 데이터들은 다른 열에는 값이 다 존재하지만 continent의 열에는 실질적으로 값이 존재하지 않는다고 해석할 수 있습니다.


05. AND, OR, NOT 연산자 사용하기

📚참고open in new window

앞서 사용했던 동일한 데이터프레임인 drink_df를 사용합니다.

데이터 로드

import pandas as pd

url = 'https://raw.githubusercontent.com/justmarkham/DAT8/master/data/drinks.csv'
drink_df = pd.read_csv(url, ',')
drink_df
drink_df

AND, OR, NOT 연산자 사용하기

ANDOR 또는 NOT과 같은 연산자를 사용하여 여러 개의 조건을 동시에 사용하거나, 조건문 자체를 반대로 해석하도록 할 수도 있습니다.

데이터프레임에서는 AND, OR, NOT은 각각 &, |, ~로 표현합니다.

  • &: AND
  • |: OR
  • ~: NOT

다시 말해 다음과 같이 사용할 수 있습니다.

  • A조건 & B조건 : A조건과 B조건 모두 만족하는 경우
  • A조건 | B조건 : A조건 또는 B조건 둘 중 하나를 만족하는 경우
  • ~A조건 : A조건을 만족하는 경우의 반대. 즉, A조건을 만족하지 않는 경우.

물론 실제로는 조건이 2개 이상일 수도 있고, 이들을 섞어서 사용하는 것도 가능하므로 위 케이스보다 다양하게 사용할 수 있습니다. 여기서는 가장 기본적인 위의 세 가지 경우에 대해서만 다뤄보겠습니다.

우선 NOT을 사용한 경우를 봅시다.

NOT 조건
drink_df[~(drink_df.continent=='EU')]
not
not

위 코드는 continent의 값이 'EU'가 아닌 경우만을 필터링하는 코드입니다.

하지만 두 가지 조건을 모두 만족하는 것이 아니라 단, 한 개만 만족하더라도 데이터를 뽑으려고 한다면 AND를 단지 OR로 바꿔주기만 하면 됩니다. 두 개의 조건은 같지만 AND를 단순히 OR로 바꾼 경우에는 데이터가 몇 개나 뽑히는지 확인해봅시다.

len(입력)

을 사용하면 입력의 길이를 계산하여 알려줍니다. 데이터프레임의 이름을 입력으로 할 경우에는 데이터프레임의 행의 길이를 출력합니다.

# OR 조건
len(drink_df[(drink_df.continent=='EU') | (drink_df.wine_servings > 300)])
#
# 45

AND 조건을 사용하였을 경우에는 행이 3개밖에 없었지만, 이번에는 45개로 훨씬 많습니다.


06. 로직과 수치 정보의 결합 & 정렬

앞서 사용했던 동일한 데이터프레임인 drink_df를 사용합니다.

데이터 로드

import pandas as pd

url = 'https://raw.githubusercontent.com/justmarkham/DAT8/master/data/drinks.csv'
drink_df = pd.read_csv(url, ',')
drink_df
drink_df
drink_df

로직과 수치 정보의 결합

drink_df에서 total_litres_of_pure_alchohol의 값이 최대값인 경우의 country 열을 출력한다면?

  1. 특정 열의 최대값을 구하는 방법이 무엇이었는지 생각해봅시다.
  2. 특정 열만을 출력하는 방법이 무엇이었는지 생각해봅시다.
# 어떤 나라가 total litres of pure alcohol의 값이 가장 클까요?
drink_df[drink_df.total_litres_of_pure_alcohol == drink_df.total_litres_of_pure_alcohol.max()]['country']
#
# 15    Belarus
# Name: country, dtype: object

우선 drink_df.total_litres_of_pure_alcohol.max()를 통해서 total_litres_of_pure_alcohol열의 최댓값을 구하고, 이를 로직에 적용하여 출력할 수 있습니다.

이번에는 drink_df에서 wine_servings의 값이 300보다 크거나, beer_servings의 값이 300보다 크거나, spirit_servings의 값이 300보다 큰 경우의 country열의 데이터를 모두 카운트하였을 때의 숫자를 출력해봅시다. 이를 구현하기 위해서 고려해야할 것은 총 두 가지입니다.

  1. 다수의 조건을 '또는'으로 한 번에 사용하는 방법이 무엇이었는지 생각해봅시다.
  2. 숫자를 '카운트' 하는 방법이 무엇이었는지 생각해봅시다.
# 이번에는 drink_df에서 wine_servings의 값이 300보다 크거나,
# beer_servings의 값이 300보다 크거나,
# spirit_servings의 값이 300보다 큰 경우를 모두 카운트하였을 때의 숫자를 출력해봅시다.
drink_df[(drink_df.wine_servings > 300) | (drink_df.beer_servings > 300) | (drink_df.spirit_servings > 300)].country.count()
#
# 18

정렬

데이터를 특정 기준으로 정렬해서 볼 수도 있습니다.

정렬해서 보는 방법은 다음과 같습니다.

데이터프레임의 이름.sort_values('정렬 기준이 되는 열의 이름')

drink_df.sort_values('beer_servings') # 특정 컬럼을 기준으로 정렬
정렬
정렬

beer_servings의 열을 보면 오름차순을 기준으로 정렬된 것을 확인할 수 있습니다. 기본적으로는 오름차순으로 정렬되지만, 만약 내림차순으로 정렬하고 싶다면 데이터프레임의 이름.sort_values('정렬 기준이 되는 열의 이름', ascending=False)sort_values의 인자로 ascending=False를 추가해주면 됩니다.

# 내림차순으로 정렬
drink_df.sort_values('beer_servings', ascending=False)

내림차순 정렬의 기준은 하나의 열이 아니라 다수의 열일 수 있습니다. 열의 이름들을 원소로하는 리스트를 sort_values의 입력으로 사용하면 해당 열들을 기준으로 정렬을 수행합니다.

# 2개의 컬럼 기준으로 정렬
drink_df.sort_values(['beer_servings', 'wine_servings'])
정렬2
정렬2

07. 상관 관계 분석

상관 분석 이란 두 변수 간의 선형적 관계를 상관 계수로 표현하는 것을 말합니다. 상관 계수를 구하는 것은 공분산의 개념을 포함하는데, 공분산은 2개의 변수에 대한 상관 정도. 2개의 변수 중 하나의 값이 상승하는 경향을 보이면 다른 값도 상승하는 경향을 수치로 표현한 것입니다. 하지만 공분산만으로 두 확률 변수의 상관 관계를 구한다면 두 변수의 단위 크기에 영향을 받을 수 있습니다. 따라서 이를 -1과 1 사이 값으로 변환합니다. 이를 상관 계수라 합니다.

만약 상관 계수가 1에 가깝다면 서로 강한 양의 상관 관계가 있는 것이고, -1에 가깝다면 음의 상관 관계가 있는 것입니다. 0이면 상관 관계가 없습니다.

Matplotlib는 파이썬에서 자료를 차트나 플롯으로 시각화하는 패키지입니다. SeabornMatplotlib을 기반으로 다양한 테마와 기능을 추가한 시각화 패키지입니다. 데이터를 시각화하기위해서 Matplotlib만을 사용할 수도 있고, Seaborn을 사용할 수도 있으며, 이 두 가지를 함께 사용할 수도 있습니다.

상관관계 분석

상관관계 분석 실습을 위해 임의로 데이터프레임을 하나 만들어봅시다.

import pandas as pd # 판다스 임포트.
import matplotlib.pyplot as plt # 맷플롯립 임포트.
import seaborn as sns # 시본 임포트

다수의 파이썬 리스트를 통해 데이터프레임을 만드는 방법은 다음과 같습니다.

pd.DataFrame({"열이름1":리스트1, "열이름2":리스트2, "열이름3":리스트3})

test_df = pd.DataFrame({"v1":[100,200,300,400], "v2":[400,200,100,250], "v3":[40,60,60,100]})
test_df

| . | v1 | v2 | v3 | | :--- | ---: | ---: ---: | | 0 | 100 | 400 | 40 | | 1 | 200 | 200 | 60 | | 2 | 300 | 100 | 60 | | 3 | 400 | 250 | 10 |

데이터의 상관계수를 구하는 방법은 다음과 같습니다.

데이터프레임의 이름.corr(method='pearson')

corr = test_df.corr(method = 'pearson')
corr

| . | v1 | v2 | v3 | | :--- | ---: | ---: ---: | | v1 | 1.000000 | -0.568038 | 0.923381 | | v2 | -0.568038 | 1.000000 | -0.291397 | | v3 | 0.923381 | -0.291397 | 1.000000 |

히트맵 그리기

데이터프레임에서 .values를 하게되면 데이터프레임의 각 행이 마치 리스트 형태로 변환되어서 출력이 됩니다. 이를 뒤에 차트를 그릴 때, 입력으로 사용할 것입니다.

corr.values
#
# array([[ 1.        , -0.56803756,  0.92338052],
#        [-0.56803756,  1.        , -0.29139712],
#        [ 0.92338052, -0.29139712,  1.        ]])

차트에 이름을 입력하기 위해서 다음과 같이 column_names라는 리스트를 만듭니다.

column_names = ['ver1', 'ver2', 'ver3']

일반적으로 상관계수 차트를 그릴 때는 seaborn에서 제공하는 heatmap()을 주로 사용합니다. seabornsns이라는 이름으로 임포트하였다면, 기본적인 사용 방법은 다음과 같습니다.

sns.heatmap(데이터프레임의 상관계수 데이터)

아래 코드에 heatmap에서 사용하는 각종 추가적인 설정값에 대해서 주석을 달아두었습니다. 이 설정값들은 필수적인 입력이 아니며, 좀 더 예쁘게 차트를 그리기 위해서 여러분들이 추가적으로 조작하는 것들입니다.

# 레이블의 폰트 사이즈를 조정
sns.set(font_scale=2.0)

test_heatmap = sns.heatmap(corr.values, # 데이터
                          cbar = False, # 오른쪽 컬러 막대 출력 여부
                           annot = True, # 차트에 숫자를 보여줄 것인지 여부
                          annot_kws={'size' : 20}, # 숫자 출력 시 숫자 크기 조절
                           fmt = '.2f', # 숫자의 출력 소수점자리 개수 조절
                           square = 'True', # 차트를 정사각형으로 할 것인지
                          yticklabels=column_names, # y축에 컬럼명 출력
                          xticklabels=column_names) # x축에 컬럼명 출력
plt.tight_layout()
plt.show()
히트맵
히트맵

이번에는 cbarTrue로 해봅시다.

# 레이블의 폰트 사이즈를 조정
sns.set(font_scale=2.0)

test_heatmap = sns.heatmap(corr.values,
                           cbar = True,
                           annot = True,
                           annot_kws={'size' : 20},
                           fmt = '.2f',
                           square = True,
                          yticklabels=column_names,
                          xticklabels=column_names)
plt.tight_layout()
plt.show()
히트맵2
히트맵2

각종 설정값이 어떻게 동작하는지 가장 쉽게 알 수 있는 방법은 해당 설정값을 지워서 재출력해보는 것입니다! 예를 들어서 fmt = '.2f'의 값을 fmt = '.3f'으로 바꿔서 재실행해보세요. 그러면 차트 위의 숫자가 소수점 셋 째자리까지 출력 될 것입니다.

다른 설정값에 대해서도 여러분들이 자유자재로 바꿔서 출력해보세요.

상관 분석을 시각화 할 수 있는 또 다른 방법을 산점도(scatter plot) 를 그리는 것입니다. 산점도는 좌표상에 점들을 표시하는 방법으로 두 개 변수 간의 관계를 나타내는 그래프 방법입니다. 변수 A가 증가할 때 변수 B 또한 증가하는 어떤 상관 관계가 있는지, 아니면 아무런 관계가 없는지 산점도를 통해서 확인해 볼 수 있습니다.

seabornsns란 이름으로 임포트하였다면, 산점도를 그리는 기본 방법은 다음과 같습니다. pairplot은 각 열의 조합에 대해서 산점도를 그리고, 같은 데이터가 만나는 대각선 영역에는 해당 데이터의 히스토그램을 그립니다.

sns.pairplot(데이터프레임)

# whitegrid = 배경에 하얀 선을 넣는다.
sns.set(style='whitegrid')
sns.pairplot(test_df)
plt.show()
페어플롯
페어플롯

지금은 데이터가 너무 적어서 산점도를 해석하기에는 무리가 있습니다. 산점도의 해석은 주류 데이터에 대해서 진행해보겠습니다!


08. 응용! 상관 관계 분석과 산점도

앞서 사용했던 동일한 데이터프레임인 drink_df를 사용합니다.

데이터 로드

import pandas as pd

url = 'https://raw.githubusercontent.com/justmarkham/DAT8/master/data/drinks.csv'
drink_df = pd.read_csv(url, ',')
drink_df
drink_df
drink_df

주류 데이터의 상관 계수

주류 데이터의 각 특성(feature)에 대해서 상관계수를 계산하고, 이를 seabornheatmappairplot을 통해서 시각화해봅시다! 우선, 'beer_servings', 'wine_servings' 두 특성 간의 상관계수를 계산해보겠습니다.

# 'beer_servings', 'wine_servings' 두 피처간의 상관계수를 계산합니다.
# pearson은 상관계수를 구하는 계산 방법 중 하나를 의미하며, 가장 널리 쓰이는 방법입니다.
corr = drink_df[['beer_servings', 'wine_servings']].corr(method = 'pearson')
corr
상관계수
상관계수

주류 데이터의 각 특성(feature)에 대해서 상관계수를 계산하고, 이를 seaborn의 heatmap과 pairplot을 통해서 시각화해봅시다! 우선, 'beer_servings', 'wine_servings' 두 특성 간의 상관계수를 계산해보겠습니다.

# 피처간의 상관계수 행렬을 구합니다.
cols = ['beer_servings', 'spirit_servings', 'wine_servings', 'total_litres_of_pure_alcohol']
corr = drink_df[cols].corr(method = 'pearson')
corr
상관계수2
상관계수2

가장 상관계수 값이 높은 경우는 beer_servingstotal_litres_of_pure_alcohold의 상관계수 값으로 0.835839에 해당됩니다. 이를 seabornheatmap을 통해서 시각화해봅시다. 우선 heatmap의 입력을 만들기 위해서 상관계수값에 .values를 적용시켜줍니다.

corr.values
#
# array([[1.        , 0.45881887, 0.52717169, 0.83583863],
#        [0.45881887, 1.        , 0.19479705, 0.65496818],
#        [0.52717169, 0.19479705, 1.        , 0.66759834],
#        [0.83583863, 0.65496818, 0.66759834, 1.        ]])

히트맵 차트의 x축과 y축에 각각의 레이블을 달아주기 위해서 다음의 리스트를 만들어줍니다.

column_names = ['beer', 'spirit', 'wine', 'alcohol']

seabornheatmap 시각화를 진행해봅시다!

import seaborn as sns
import matplotlib.pyplot as plt

# 레이블의 폰트 사이즈를 조정
sns.set(font_scale=1.5)
hm = sns.heatmap(corr.values,
            cbar=True,
            annot=True, 
            square=True,
            fmt='.2f',
            annot_kws={'size': 15},
            yticklabels=column_names,
            xticklabels=column_names)

plt.tight_layout()
plt.show()
상관계수3
상관계수3
  • cbar는 오른쪽에 있는 막대(범주)를 표시하는 것입니다.
  • annot는 상관계수를 표시합니다.
  • square는 정사각형으로 지정하는 것이며, False로 설정 시 직사각형이 됩니다.
  • fmt는 상관계수의 소수점 자리수를 지정합니다.
  • annot_kws는 상관계수의 글자 크기를 지정합니다.
  • yticklabels는 y축 레이블 값을 의미합니다.
  • xticklabels는 x축 레이블 값을 의미합니다.

alcohol은 대체적으로 다른 특성들과 모두 상관 계수가 높습니다. 이미 시각화를 진행하기 전에도 확인한 내용이기는 하지만, 그 중에서도 0.84로 beer와 상관 계수값이 가장 높습니다. 앞서 확인하였을 때는 0.835839였지만, 여기서는 소수점 두 자리까지만 출력하면서 반올림되어 0.84로 출력된 것이라 보면 됩니다.

피어슨의 상관계수는 일반적으로,

  • 값이 -1.0 ~ -0.7 이면, 강한 음적 상관관계 값이 -0.7 ~ -0.3 이면, 뚜렷한 음적 상관관계
  • 값이 -0.3 ~ -0.1 이면, 약한 음적 상관관계
  • 값이 -0.1 ~ +0.1 이면, 없다고 할 수 있는 상관관계
  • 값이 +0.1 ~ +0.3 이면, 약한 양적 상관관계
  • 값이 +0.3 ~ +0.7 이면, 뚜렷한 양적 상관관계
  • 값이 +0.7 ~ +1.0 이면, 강한 양적 상관관계 로 해석됩니다.

산점도

주류 데이터의 산점도. 즉, pairplot을 확인해봅시다. pairplot은 데이터프레임을 인수로 받아 그리드(grid) 형태로 각 데이터 열의 조합에 대해 산점도를 그립니다. 같은 데이터가 만나는 대각선 영역에는 해당 데이터의 히스토그램을 그립니다.

# 시각화 라이브러리를 이용한 피처간의 scatter plot을 출력합니다.
sns.set(style='whitegrid')
sns.pairplot(drink_df[['beer_servings', 'spirit_servings', 
                     'wine_servings', 'total_litres_of_pure_alcohol']])
plt.show()
산점도
산점도

09. 데이터 탐색하기 (결측값 제거, 시각화, 통계 확인)

앞서 사용했던 동일한 데이터프레임인 drink_df를 사용합니다.

데이터 로드

import pandas as pd

url = 'https://raw.githubusercontent.com/justmarkham/DAT8/master/data/drinks.csv'
drink_df = pd.read_csv(url, ',')
drink_df
drink_df
drink_df

결측값 제거

결측값은 탐색적 데이터 분석에서도, 그 후 더 나아가 머신 러닝 알고리즘을 통해 분석을 할 때에도 성능에 영향을 줄 수 있는 값입니다. 결측값은 아예 제거를 해주거나, 특정 값으로 채워주거나 크게 두 가지 선택을 해줄 수 있습니다.

제거할 때는 주로 dropna()를 쓰는 반면, 채워줄 때는 주로 fillna()를 사용합니다. 앞서 continent열에 결측값이 23개가 있는 것을 확인했었습니다. 여기서는 특정값으로 채우는 fillna()를 사용해보겠습니다.

# 결측 데이터를 특정값으로 채우는 방법은 .fillna()를 사용하는 것이다.
# 이 경우 기타 값이라는 의미에서 'ETC'를 넣어준다.
drink_df['continent'] = drink_df['continent'].fillna('ETC')
drink_df.sample(10)
continent_etc
continent_etc

continent열에 결측값이 있을 경우 'ETC'라는 값으로 채우도록 했습니다. 채우고 나서 랜덤으로 10개를 출력하도록 해보았는데, ETC란 값이 중간에 보입니다. 이제 결측값이 사라졌는지 앞서 배운 코드인 isnull().sum()으로 확인해봅시다.

drink_df.isnull().sum()
#
# country                         0
# beer_servings                   0
# spirit_servings                 0
# wine_servings                   0
# total_litres_of_pure_alcohol    0
# continent                       0
# dtype: int64

이제 continent 열에는 결측값이 존재하지 않습니다.

파이차트 그리기

파이 차트를 그리려면

  • 파이 차트로 사용할 데이터의 이름이 담긴 리스트,
  • 그리고 해당 이름에 해당하는 데이터의 값이 담긴 리스트

이 두 가지가 필요합니다. 여기서는 continent에 대해서 파이차트를 그려보고자 합니다. 파이 차트의 값으로는 continent에 있는 각 값들의 개수를 사용하고자 하구요. continent 열에 대해서 value_counts()를 사용하였을 때의 결과를 출력해보고, 타입을 확인해봅시다.

drink_df['continent'].value_counts()
#
# AF     53
# EU     45
# AS     44
# ETC    23
# OC     16
# SA     12
# Name: continent, dtype: int64

type(drink_df['continent'].value_counts())
#
# pandas.core.series.Series

인덱스에는 continents의 이름, 그리고 실제 값 부분에는 해당 continents에 있는 값들을 각각 카운트한 값이 들어가있습니다. 인덱스는 index로 접근하고 값에 대해서는 values로 접근하므로 각각 접근 후에 리스트로 값을 변환하는 tolist()를 사용해줍니다.

pie_labels = drink_df['continent'].value_counts().index.tolist()
pie_values = drink_df['continent'].value_counts().values.tolist()
print(pie_labels)
print(pie_values)
#
# ['AF', 'EU', 'AS', 'ETC', 'OC', 'SA']
# [53, 45, 44, 23, 16, 12]

이제 파이 차트를 그리기 위해 필요한 두 가지를 얻었습니다. 파이 차트를 그리는 방법은 plt.pie()를 사용합니다. autopct는 소수점을 몇 개까지 출력할지를 정하기 위한 값입니다.

사용 방법은 다음과 같습니다.

plt.pie(데이터의 실질적인 값, labels=데이터의 레이블 리스트)

autopct는 소수점을 몇 개까지 출력할지를 정하기 위한 값입니다.

plt.pie(pie_values, labels=pie_labels, autopct='%.02f%%')
plt.title('Percentage of each continent')
plt.show()
파이차트
파이차트

GroupBy를 이용한 통계 확인

GroupBy는 특정 값을 기준으로 그룹핑한 후에 그룹 별로 통계적인 수치 정보를 구할 수 있도록 하는 방법입니다. 사용 방법은 다음과 같습니다.

데이터프레임의 이름.groupby('그룹핑 기준이 되는 열')['보고자 하는 열'].통계 함수

GroupBy를 통해서 수치 정보를 계산해봅시다.

  • 어떤 대륙이 평균적으로 맥주를 더 먹을까?

대륙에 대한 열은 continent에 있습니다. 그리고 맥주의 소비량은 beer_servings 열입니다.

평균을 구하는 함수는 mean()이므로 다음과 같이 사용합니다.

어떤 대륙이 평균적으로 맥주를 더 먹을까요?

drink_df.groupby('continent')['beer_servings'].mean()
#
# continent
# AF      61.471698
# AS      37.045455
# ETC    145.434783
# EU     193.777778
# OC      89.687500
# SA     175.083333
# Name: beer_servings, dtype: float64

type(drink_df.groupby('continent')['beer_servings'].mean())
#
# pandas.core.series.Series

각 대륙 별로 와인 소비에 대한 통계 정보를 출력해볼까요?

drink_df.groupby('continent')['wine_servings'].describe()
wine
wine

모든 컬럼에 대해서 대륙별로 평균 알콜 소비량을 출력해볼까요

drink_df.groupby('continent').mean()
alcohol_usage
alcohol_usage

전체 평균보다 많은 알코올을 섭취하는 대륙을 구해봅시다.

total_mean = drink_df.total_litres_of_pure_alcohol.mean()
continent_mean = drink_df.groupby('continent')['total_litres_of_pure_alcohol'].mean()
continent_over_mean = continent_mean[continent_mean >= total_mean]
print(continent_over_mean)
# 
# continent
# ETC    5.995652
# EU     8.617778
# SA     6.308333
# Name: total_litres_of_pure_alcohol, dtype: float64

평균 wine_servings이 가장 높은 대륙을 구해봅시다.

beer_continent = drink_df.groupby('continent').wine_servings.mean().idxmax()
print(beer_continent)

10. Quiz

Quiz 1

sklearn은 머신 러닝 패키지로 각종 머신 러닝 데이터셋도 제공하고 있습니다. 이번 퀴즈에서 사용할 머신 러닝 데이터셋은 iris data(붓꽃 데이터)입니다.

# 퀴즈에 필요한 패키지는 아래의 두 가지가 전부가 아닙니다.
# 여러분들의 필요에 따라서 패키지를 지속 추가하셔도 됩니다.
import pandas as pd
from sklearn import datasets

# 붓꽃 데이터 로드
iris_data = datasets.load_iris()

iris 데이터셋의 각 열은 다음과 같습니다.

  • Sepal Length : 꽃받침의 길이 정보
  • Sepal Width : 꽃받침의 너비 정보
  • Petal Length : 꽃잎의 길이 정보
  • Petal Width : 꽃잎의 너비 정보

이러한 4개의 특성을 알려줄테니

꽃의 종류를 예측해보라는 것이 iris 데이터셋이 제시하는 머신러닝 문제입니다.

  • Species 꽃의 종류 정보 : setosa / versicolor / virginica 의 3종류.

우선 4개의 특성에 해당되는 데이터를 df_data에 로드합니다.

df_data = pd.DataFrame(iris_data['data'], columns=iris_data['feature_names'])
df_data.sample(5)
sample
sample

4개의 열이 출력되는데, 각각 꽃받침의 길이 정보, 꽃받침의 너비 정보, 꽃잎의 길이 정보, 꽃잎의 너비 정보에 해당됩니다. iris 데이터(붓꽃 데이터)는 이 4개의 특성만 보고 이 꽃이 어떤 품종의 붓꽃인지를 예측해야하는 머신 러닝 문제를 위한 데이터셋입니다. 정답에 해당되는 레이블을 df_target에 로드합니다.

df_target = pd.DataFrame(iris_data['target'], columns=['species'])
df_target.sample(5)
species
species

이 시리즈에는 어떤 값들이 있는지 출력해봅시다.

# 값의 종류를 전부 출력
df_target['species'].unique()
array([0, 1, 2])

숫자가 0, 1, 2가 나오는데 각각이 의미하는 품종은 다음과 같습니다.

  • 0 = setosa
  • 1 = versicolor
  • 2 = virginica

pd.concat()을 사용하면 위 데이터프레임과 시리즈를 하나의 데이터프레임으로 합치는 것이 가능합니다.

df = pd.concat([df_data, df_target], axis=1)
df
퀴즈
퀴즈

이렇게 150개의 행과 5개의 열을 가진 붓꽃 데이터가 데이터프레임에 로드된 상태입니다.

총 150개의 붓꽃이 있는데, 종류는 3개였고,

각 붓꽃의 꽃받침의 길이와 너비, 꽃잎의 길이와 너비를 기록한 데이터셋인 것이죠.

여러분들의 퀴즈는 이 붓꽃 데이터를 가지고 데이터프레임의 기능(조건부 필터링, 상관 계수 분석 등), Matplotlib, Seaborn을 사용하여 각종 통계 정보를 구하고, 시각화를 해보는 것입니다. 단 하나의 정답이 존재하는 것은 아닙니다. 여러분들이 배운 내용들을 복습하여 최대한 성실하게 퀴즈를 수행해보세요.


이찬희 (MarkiiimarK)
Never Stop Learning.