0. 주의사항 : 아래의 내용을 무조건적으로 신뢰하지 않기를 바란다. 프로그래밍의 세계는 매우 심오하고 복잡해서 지금 정리하는 내용이 완벽하다고 나조차도 신뢰하지 않기 때문이다.
1. 일자 : 2022년 1월 5일 14:00 ~ 18:00 (3일차)
2. 주제 : Pandas 기초 정리
3. 내용
(1) Pandas 시작하기
Pandas를 설치하기 위해서는 명령 프롬프트에 아래 명령어를 입력하면 손쉽게 설치가 가능하다.
pip install pandas
Python 내에서 Pandas를 사용하기 위해서는 아래 코드를 입력한다.
import pandas
# pandas 내 함수를 쉽게 사용하고 싶으면 아래 코드를 입력하자.
# 기본적으로 아래의 형태를 더 많이 이용하는 편이다.
import pandas as pd
# 이후 사용자는 pd.(pandas 내 함수)와 같이 손쉽게 사용이 가능하다.
(2) Pandas로 1차원 데이터 다루기 : Series
Series란, 쉽게 말해 1차원적인 데이터 형태라고 보면 된다. 인덱스를 임의의 값으로 지정해줄 수 있다.
Series는 "pd.Series([값1, 값2, 값3...])"의 형태로 선언할 수 있다.
s = pd.Series([1, 4, 9, 16, 25])
s
# <출력결과>
# 0 1
# 1 4
# 2 9
# 3 16
# 4 25
# dtype: int64
앞에서 이야기했듯, Series는 인덱스를 임의의 값으로 지정해줄 수 있다.
# dictionary형으로 값을 넣어보자.
t = pd.Series({"one" : 1, "two" : 2, "three" : 3, "four" : 4})
t
# <출력결과>
# one 1
# two 2
# three 3
# four 4
# dtype: int64
# index가 one, two, three...처럼 바뀐 것을 알 수 있다.
(2.1) Series + Numpy
Series를 다루다 보면 Numpy의 ndarray와 유사한 것을 알 수 있다.
먼저 Series는 index를 통한 접근이나 Slicing이 가능하다.
s = pd.Series([1, 4, 9, 16, 25])
t = pd.Series({"one" : 1, "two" : 2, "three" : 3, "four" : 4})
s[1]
t[1]
# 출력결과
# 4
# 2
# index값을 one, two, three...처럼 바꾸더라도
# 일반적인 index방식으로도 접근이 가능한 것을 확인할 수 있다.
t = pd.Series({"one" : 1, "two" : 2, "three" : 3, "four" : 4})
t[1:3]
# <출력결과>
# two 2
# three 3
# dtype: int64
참고로 Series에는 특별한 기능이 있다.
첫째, 인덱스에 조건식을 넣어 참인 값만 출력할 수 있는 기능이 존재한다. 사용자는 이 기능을 활용한다면 데이터 필터링을 보다 쉽게 진행할 수 있다. 아래 코드를 살펴보자.
# Series의 특별한 기능 : 조건을 부여한 인덱스를 적용하는 것이 가능
# s값이 자신의 중앙값보다 큰 값만 출력하기
s = pd.Series([1, 4, 9, 16, 25])
s[s > s.median()]
# <출력결과>
# 3 16
# 4 25
# dtype: int64
둘째, 입력받은 인덱스 순으로 결과값을 출력할 수 있다.
s = pd.Series([1, 4, 9, 16, 25])
s[[3, 1, 4]]
# <출력결과>
# 3 16
# 1 4
# 4 25
# dtype: int64
# 입력받은 인덱스 순으로 결과값이 출력되는 것을 알 수 있다
셋째, numpy 함수를 사용할 수도 있다.
#numpy 함수를 사용하는 것도 가능하다
import numpy as np
s = pd.Series([1, 4, 9, 16, 25])
np.exp(s)
# <출력결과>
# 0 2.718282e+00
# 1 5.459815e+01
# 2 8.103084e+03
# 3 8.886111e+06
# 4 7.200490e+10
# dtype: float64
참고로 Series의 데이터 타입을 확인하기 위해서는 .dtype을 이용하도록 하자.
s = pd.Series([1, 4, 9, 16, 25])
s.dtype
# <출력결과>
# dtype('int64')
(2.2) Series + dictionary
앞서 index에 (임의의 값)을 넣는 부분을 살펴보면 그 형태가 마치 dictionary와 비슷한 것을 확인할 수 있었다. 실제로 Series는 dictionary와 비슷한 부분이 많이 존재한다.
첫째, Series의 값을 가져올 때 'key값'(Series에서는 값이 변경된 "index")을 이용해서 값을 가져올 수 있다. 값을 가져오는 방법은 (1) [index값]을 통해서 찾는 방법과 (2) .get을 통해서 가져오는 방법 두 가지가 있다.
하나, [index값]을 이용해서 찾는 방법
[index값]을 이용해서 찾는 방법의 경우 아래와 같이 결과가 잘 나오는 것을 알 수 있다.
t = pd.Series({"one" : 1, "two" : 2, "three" : 3, "four" : 4})
t['one']
# <출력결과>
# 1
그러나, 만약 없는 index값을 사용해서 값을 찾는 경우 [index값]의 방법은 오류를 반환한다.
t = pd.Series({"one" : 1, "two" : 2, "three" : 3, "four" : 4})
t['seven']
# <출력결과>
# KeyError Traceback (most recent call last)...
둘. .get을 통해서 값을 찾는 방법
.get을 이용해서 값을 찾을 경우, [index값]을 이용해서 찾는 방법과 크게 다르지 않으나, .get의 경우는 Series 내 '없는 index값'을 사용하여 값을 탐색하더라도 오류를 반환하지 않는다는 장점이 있다.
t = pd.Series({"one" : 1, "two" : 2, "three" : 3, "four" : 4})
print(t.get('one'))
# 만약 대상 Series 내 원하는 index값이 없으면 값을 반환하지 않는다
print(t.get('seven')) : None
# 사용자는 원하는 index 다음에 임의의 값을 적음으로써
# 값을 반환하지 못할 경우 출력할 default값을 지정할 수 있다.
print(t.get('seven', 0)) : 0
# <출력결과>
# 1
# None
# 0
둘째, 값을 추가할 때도 마치 dictionary처럼 키와 값을 함께 넣는 것이 가능하다.
t = pd.Series({"one" : 1, "two" : 2, "three" : 3, "four" : 4})
t['five'] = 5
t
# <출력결과>
# one 1
# two 2
# three 3
# four 4
# five 5
# dtype: int64
만약 index가 존재하는지를 확인하고 싶다면 'in'을 사용하면 된다. 만약 대상 index가 존재하면 True를 반환하고, index가 존재하지 않으면 False를 반환한다.
# in을 사용하여 index 확인
t = pd.Series({"one" : 1, "two" : 2, "three" : 3, "four" : 4})
'five' in t
'six' in t
# <출력결과>
# True
# False
(2.3) Series에 이름 붙이기
Series에는 'name'속성을 가지고 있는데, 사용자는 언제든 Series에 이름을 부여할 수 있다.
# 임의의 수 5개로 이루어진 Series를 생성한다.
# 이름은 "random_num"으로 설정한다.
s = pd.Series(np.random.randn(5), name = "random_nums")
s
# <출력결과>
# 0 0.163255
# 1 0.857209
# 2 1.006797
# 3 -0.904903
# 4 0.834058
# Name: random_nums, dtype: float64
Series를 만든 후 이름을 부여하거나, 혹은 이미 지정된 이름을 변경하는 것도 가능하다.
s.name = "임의의 난수"
s
# <출력결과>
# 0 0.163255
# 1 0.857209
# 2 1.006797
# 3 -0.904903
# 4 0.834058
# Name: 임의의 난수, dtype: float64
(3) Pandas로 2차원 데이터 다루기 - dataframe
dataframe은 가로 * 세로의 2차원적 데이터 형태이다. dataframe을 만들기 위해서는 dictionary를 먼저 생성한 후, dataframe화하는 과정이 필요하다. dataframe 안에는 텍스트, 숫자, 날짜 등 어떤 값이든지 담을 수 있다.
d = {"height" : [1, 2, 3, 4], "weight" : [30, 40, 50, 60]}
df = pd.DataFrame(d)
df
# <출력결과>
# height weight
#0 1 30
#1 2 40
#2 3 50
#3 4 60
dataframe 안에 들어있는 데이터 타입을 확인하기 위해서는 .dtypes을 이용하면 된다. dtypes는 각 column별로 데이터타입을 보여준다.
df.dtypes
# <출력결과>
# height int64
# weight int64
# dtype: object
Pandas에서도 R과 마찬가지로 csv를 dataframe으로 가져오는 기능을 지원한다. '.read_csv("파일이 존재하는 경로/파일명.csv")'을 이용하면 되는데, 만약 코드를 작성하는 파일과 불러오고자 하는 파일이 동일 경로 내 존재할 경우, "./호출하려는 파일명"만 적으면 된다. 그렇지 않으면 "파일이 존재하는 경로"까지를 모두 포함하여 적어주어야 한다. 내 경우에는 파일이 존재하는 경로까지 모두 적어서 호출하였다.
실습 파일은 keggle에 있는 covid19 관련 자료를 활용하였다.
# 동일 경로에 호출하고자 하는 파일이 존재할 경우
# pd.read_csv()에서 ()안에 "./호출하려는 파일명"을 적는다.
# 그렇지 않을 경우, 호출하고자 하는 파일 경로와 파일명을 함께 적는다.
covid = pd.read_csv("파일이 존재하는 경로\country_wise_latest.csv")
covid
# <출력결과>는 생략한다. 원하는대로 dataframe이 출력되는 것을 확인할 수 있다.
(4) Pandas의 활용
(4.1) 일부분만 관찰하기
데이터의 행은 최소 수백 개로 구성되어 있다 보니 모든 데이터를 확인하기 힘들다. 그래서 데이터를 위 혹은 아래에서부터 몇 개만 골라 관찰하여 데이터의 상태를 간단하게 확인하는 것으로 분석을 시작한다. .head(), .tail()을 이용한다.
# 위에서부터 n개의 데이터를 관찰한다.
# 괄호 내 일정한 숫자를 적으면 해당 숫자만큼의 개수를 출력한다.
# 아무것도 적지 않으면 5개가 출력된다.
# (1) covid Dataframe 중 위에서부터 7개를 출력한다.
covid.head(7)
# (2) covid Dataframe 중 위에서부터 5개를 출력한다.
covid.head()
# 아래서부터 n개의 데이터를 관찰한다.
# 괄호 내 일정한 숫자를 적으면 해당 숫자만큼의 개수를 출력한다.
# 아무것도 적지 않으면 5개가 출력된다.
# (1) covid Dataframe 중 아래서부터 7개를 출력한다.
covid.tail(7)
# (2) covid Dataframe 중 아래서부터 5개를 출력한다.
covid.tail()
참고로 head()나 tail()만 보고 모든 데이터를 단정 짓는 행동은 매우 위험하다. head()와 tail()는 수많은 데이터 중 극히 일부만 보여주기 때문에 모든 부분을 확인하지 못한다는 점을 항상 인지하고 조심히 데이터를 다루도록 하자.
(4.2) 데이터 접근하기
데이터에 접근하기 위해서는 "(dataframe 이름)['열 이름']" 혹은 "(dataframe 이름).(열 이름)" 방식을 사용한다. 다만 후자의 경우, (열 이름)에 띄어쓰기가 포함되어 있을 경우 오류를 반환할 수 있으니 되도록이면 전자의 방식을 사용하도록 하자. 이때 반환된 값은 모두 Series타입을 가진다. 그러므로 우리가 앞서 진행했던 Series에서의 indexing, Slicing 등을 모두 활용하여 재가공이 가능하다.
# (1) dataframe_name.['column_name']
covid['Active']
# <(1)의 출력결과>
# 0 9796
# 1 1991
# 2 7973
# 3 52
# 4 667
# ...
# 182 6791
# 183 1
# 184 375
# 185 1597
# 186 2126
# Name: Active, Length: 187, dtype: int64
# (2) dataframe_name.Column_name
covid.Active
# <(2)의 출력결과>
# 0 9796
# 1 1991
# 2 7973
# 3 52
# 4 667
# ...
# 182 6791
# 183 1
# 184 375
# 185 1597
# 186 2126
# Name: Active, Length: 187, dtype: int64
# (3) Series타입으로 반환된 값의 Slicing
covid['Confirmed'][1:5]
# <(3) 출력결과>
# 1 4880
# 2 27973
# 3 907
# 4 950
# Name: Confirmed, dtype: int64
(4.3) "조건"을 이용해서 데이터에 접근하기
우리가 '신규 확진자(New cases)가 100명이 넘는 나라를 찾는다'라고 가정하자. 이때 사용자는 두 번의 과정을 거쳐서 데이터를 추출한다. (1) 먼저 대상이 되는 데이터를 True/False 형태로 골라낸 후, (2) True인 데이터만 출력시키는 방식이다. 아래 내용을 참고해보자.
# (1) 데이터 골라내기
# Dataframe_name["Column_name"]의 조건
covid['New cases'] > 100
# <출력결과>
# 조건에 부합하는 행이 True, 부합하지 않는 행이 False로 표시된다.
# 0 True
# 1 True
# 2 True
# 3 False
# 4 False
# ...
# 182 True
# 183 False
# 84 False
# 185 False
# 186 True
# Name: New cases, Length: 187, dtype: bool
# (2) 데이터 필터링
# Dataframe_name[Dataframe_name['Column_name']의 조건]
covid[covid['New cases'] > 100]
# <출력결과>는 생략. True에 해당하는 행 전체가 무사히 출력된다.
만약 어느 속성의 범주를 확인하고 싶다면 .unique()를 사용해보자. 이 함수는 지정한 column에서 사용된 값 중 중복값을 모두 제거하여 보여준다.
covid['WHO Region'].unique()
# <출력결과>
# array(['Eastern Mediterranean', 'Europe', 'Africa', 'Americas',
# 'Western Pacific', 'South-East Asia'], dtype=object)
(4.4) 행을 기준으로 데이터 접근하기
해당 파트를 설명하기에 앞서 임의의 dataframe을 새로 생성해보도록 하자.
# 예시 데이터 - 도서관 정보
books_dict = {"Avaliable" : [True, True, False], "Location" : [102, 215, 323], "Genre" : ["Programming", "Physics", "Math"]}
df_books_dict = pd.DataFrame(books_dict, index = ['버그란 무엇인가', '두근두근 물리학', '미분해줘 홈즈'])
df_books_dict
# <출력결과>
# Avaliable Location Genre
# 버그란 무엇인가 True 102 Programming
# 두근두근 물리학 True 215 Physics
# 미분해줘 홈즈 False 323 Math
사용자는 index를 이용해서 값을 가져올 수 있는 방법이 있다. 만약 변경한 index값을 그대로 이용하고 싶으면 ".loc[row, col]을 활용하면 된다. 이렇게 출력된 데이터는 Series타입을 가진다.
# Dataframe_name.loc[row, col]
# (1) "버그란 무엇인가"에 대한 내용 전부 출력
df_books_dict.loc["버그란 무엇인가"]
# <(1)의 출력결과>
# Avaliable True
# Location 102
# Genre Programming
# Name: 버그란 무엇인가, dtype: object
# "열"부분을 비워놓으니 자동으로 모든 열이 선택되어
# 결과적으로 행 전체가 출력된 모습이다.
# (2) 미분해줘 홈즈 책이 대출가능한지 확인
# 행/열 형태로 출력하면 나온다.
df_books_dict.loc["미분해줘 홈즈"]['Avaliable']
df_books_dict.loc["미분해줘 홈즈", 'Avaliable']
# <(2)의 출력결과>
# False
# 따로 []를 씌워서 표현하든, 콤마로 분리하여 표현하든 결과값은 동일하다.
그러나 변경한 index값이 아닌, 숫자 index를 이용해서 값을 가져오고 싶으면 ".iloc[row_index_num, col_index_num]을 사용하면 된다. 이렇게 출력한 데이터의 타입 또한 Series이다.
# (1) 0행, 1열의 정보 가져오기
df_books_dict.iloc[0, 1]
# (1)의 출력결과 : 102
# (2) 1행, 0~1열의 정보 가져오기
df_books_dict.iloc[1, 0:2]
# <(2)의 출력결과>
# Avaliable True
# Location 215
# Name: 두근두근 물리학, dtype: object
(4.5) groupby
groupby는 총 세 단계에 걸쳐 수행된다.
(1) 특정한 기준을 바탕으로 DataFrame을 분할한 뒤(Split)
(2) 각종 통계함수(sum(), mean(), median() 등)를 적용해서 각 dataFrame을 통계 함수의 기능에 맞게 압축한다(Apply.)
(3) 마지막으로 Apply된 결과를 바탕으로 새로운 Series를 생성한다. 생성된 Series는 "group_key : apply_value"의 형태를 가진다. (Combine)
우리는 지금부터 위 과정을 "WHO Region별 확진자 수 구하기" 예제를 통해 자세히 알아보고자 한다.
# (1) Split
# covid에서 대상 column을 추출하여 groupby를 수행한다.
# 여기에서는 계산할 열 "확진자수(Confirmed)"를 추출하여
# 대상 열 "Who Region"을 기준으로 groupby한다.
covid_by_region = covid['Confirmed'].groupby(by = covid["WHO Region"])
covid_by_region
# 현재는 split만 적용된 형태이므로 dataFrame이 분산되어 의미있는 값을 출력할 수 없다.
# 객체형태로 출력되는 것을 확인할 수 있다.
# <(1)의 출력결과>
<pandas.core.groupby.generic.SeriesGroupBy object at 0x000001E1A6729EA0>
# (2) Apply
# 원하는 목적에 맞추어 통계함수를 적용하는 단계이다.
# 여기서는 "WHO Region"을 기준으로 "확진자수(Confirmed)"를 확인해야 하므로
# WHO Region에 맞추어 각 나라의 확진자수를 더해야 한다.
covid_by_region.sum()
# (3) Combine
# Apply를 진행한 결과이다.
# <(2)의 출력결과 = combine>
# WHO Region
# Africa 723207
# Americas 8839286
# Eastern Mediterranean 1490744
# Europe 3299523
# South-East Asia 1835297
# Western Pacific 292428
# Name: Confirmed, dtype: int64
(5) Mission
(5.1) Covid 데이터에서 100 case 대비 사망률(Deaths / 100 Cases)이 가장 높은 국가는 어디인가?
covid = pd.read_csv('"데이터가 존재하는 경로"\country_wise_latest.csv')
# "Deaths / 100 Cases" 값 중 가장 높은 값을 찾은 후
# 해당 값에 해당하는 행 전체를 출력하는 방법을 사용하였다.
Max_num = covid['Deaths / 100 Cases'].max()
covid[covid['Deaths / 100 Cases'] == Max_num]
# 혹은 .idxmax()를 활용해도 무방하다. (최대값의 인덱스를 반환)
result = covid.loc[covid['Deaths / 100 Cases'].idxmax()]
print(result)
# 따라서 100 case 대비 사망률이 가장 높은 국가는 Yemen이다.
# 위 방법은 Dataframe 형태로 값이 추출되는 반면
# 아래 방법은 Series형태로 값이 추출되는 것을 확인할 수 있었다.
(5.2) Covid 데이터에서 신규 확진자가 없는 나라 중 WHO Region이 'Europe'인 국가를 모두 출력하면?
hint. 단, 한 줄에 두 가지 조건을 Apply하는 경우, Warning이 발생할 수 있음.
나는 이 문제를 풀 때 힌트를 보고서 'groupby'로 풀어야 하는 문제라는 것을 직감했다. 왜냐하면 Apply라는 단어를 사용하는 곳은 groupby파트가 유일했기 때문이다. 그러나 아무리 고민을 해보아도 답이 나오지 않아서, 결국 데이터 필터링을 통하여 간접적으로 해결하였다.
# 신규확진자가 없는 나라를 출력 (신규확진자 = New cases)
no_new_cases = covid[covid['New cases'] == 0]
# 이 중에서 WHO Region이 유럽인 나라를 모두 츌력
Europe_no_new_cases = no_new_cases[no_new_cases['WHO Region'] == 'Europe']
Europe_no_new_cases
(5.3) 다음 데이터(아보카도 가격)를 이용해 각 Region별로 아보카도가 가장 비싼 평균가격(AveragePrice)를 출력하면?
avocado = pd.read_csv("데이터가 존재하는 경로/avocado.csv")
# 데이터 상태 확인
avocado.head()
# Split
avocado_region = avocado['AveragePrice'].groupby(by = avocado['region'])
# Apply
avocado_region.max()
# Series형태로 값이 출력되는 것을 확인할 수 있었다.
'전문지식 함양 > TIL' 카테고리의 다른 글
[프로그래머스 겨울방학 인공지능 과정] 1주차 실습과제 (0) | 2022.01.09 |
---|---|
[프로그래머스 겨울방학 인공지능 과정] Matplotlib 기초 (0) | 2022.01.07 |
[프로그래머스 겨울방학 인공지능 과정] Pandas 실습 (0) | 2022.01.06 |
[프로그래머스 겨울방학 인공지능 과정] Numpy 기초와 선형대수 정리 (0) | 2022.01.04 |
[프로그래머스 겨울방학 인공지능 과정] 기초1. github와 jupyter note의 설치 (0) | 2022.01.04 |