이번 시간의 목차
1. 판다스가 무엇일까?
2. 판다스에 대해 좀 더 알고 싶어!
3. CSV 파일을 다뤄 보자!
4. 데이터 구조 : 시리즈와 데이터 프레임
5. 판다스로 데이터 파일을 읽고 데이터프레임으로 만들자!
6. 데이터프레임과 파이플롯의 합작!
7. 판다스로 새로운 열 삽입하기
8. 판다스로 데이터를 분석해 보자!
9. 특정한 값과 조건에 맞게 골라내 보자!
10. 결손값을 찾고 삭제하고 메우자!
11. 데이터의 구조를 바꾸고 합쳐보자!
12. 마무리
자, 가자! 파이썬의 세계로!
판다스가 무엇일까?
데이터를 다루는 데에 있어서 데이터 과학자가 아닌 일반인이 가장 흔하게 접하는 것이 마이크로소프트사의 엑셀(Excel)일 것이다. 행과 열로 이루어진 표에 입력된 데이터를 처리하는 데에 탁월한 성능을 보인다.
데이터 과학자는 전통적으로 이와 같은 테이블 형태의 데이터를 선호한다. 2차원 행렬(Matrix) 형태의 데이터가 있으면 개발자가 편리하게 각 요소와 행, 열에 접근할 수 있기 때문이다.
넘파이에서 이미 2차원 행렬 형태의 데이터를 지원하고 있긴 하지만, 이 넘파이는 데이터의 속성을 표시하는 행과 열의 레이블(Label)을 갖고 있지 않아서 조금 불편하다. 지금까지 살펴본 이 넘파이나 리스트 등의 기능은 데이터를 가지고 연산 및 가공하는 데에 우수한 능력을 보여주지만, 테이블 형태의 데이터에 대한 통계적 분석이나 데이터 항목 간 연산 등에는 적합하지 않다. 파이썬은 범용 프로그래밍 언어이므로 데이터 분석을 목적으로 만들어진 다른 언어보다는 조금 떨어지기도 한다.
그래서 우리는 이제 판다스(Pandas, 계량경제학 용어의 Panel Data에서 유래.) 패키지를 이용해서 데이터를 다뤄보려 한다. 판다스는 넘파이를 기반으로하는 패키지라 처리속도가 매우 빠르고, 행과 열로 구조화된 데이터 프레임(Data Frame)을 사용하고, 다양한 함수 역시 지원한다. 행과 열에 레이블도 붙일 수 있고, 그렇게 만들어진 데이터 프레임을 결합하거나 변경하는 일도 쉽게 수행할 수 있다. 이 판다스는 저번 시간에 살펴본 pyplot과도 통합되어 있어서 데이터 과학자들을 위한 핵심 도구로 사용된다.
판다스에 대해 좀 더 알고 싶어!
판다스의 특징을 좀 더 살펴보자면 아래와 같은 큰 네 가지 특징으로 설명할 수 있다.
- 빠르고 효율적이며 다양한 표현력을 갖춘 자료구조
- 다양한 형태의 데이터에 적합
- 이종(Heterogeneous) 자료형의 열을 가진 테이블 데이터
- 시계열 데이터
- 레이블을 가진 다양한 행렬 데이터
- 다양한 관측 통계 데이터
- 핵심 구조
- 시리즈(Series) : 1차원 구조를 가진 하나의 열
- 데이터 프레임(DataFrame) : 복수의 열을 가진 2차원 데이터
- 판다스가 잘 하는 일
- 결측 데이터 처리
- 데이터의 추가 및 삭제
- 데이터 정렬과 조작
물론 이보다 많은 일을 할 수도 있다.
판다스를 이용하면 CSV파일, 엑셀 파일, SQL 데이터베이스에서 데이터를 읽고 스프레드시트 테이블과 유사한 데이터 프레임이라는 파이썬의 객체를 만들 수 있다. 데이터 프레임을 이용하면 리스트에서 for문으로 항목 하나하나를 꺼내는 것처럼 복잡한 일을 할 필요가 없다. 아래는 판다스가 하는 일을 간략하게 정리한 표이다.
하는 일 | 데이터 불러오기&저장하기 |
데이터 보기 및 검사 | 필터, 정렬 및 그룹화 | 데이터 정제 |
세부 사항 | * 리스트, 딕셔너리, 넘파이 배열을 데이터 프레임으로 변환. * csv, tsv, 엑셀 파일 등을 열 수 있음. * URL을 통해 웹 사이트의 csv나 JSON과 같은 원격 파일 혹은 데이터베이스를 열 수 있음. |
* df.mean()으로 모든 열의 평균 계산. *df.corr()로 데이터 프레임의 열 사이의 상관 관계 계산. *df.count()로 각 데이터 프레임 열에서 null이 아닌 값의 개수 계산. |
* df.sort_values()로 데이터 정렬. *조건을 사용한 열 필터링. *groupby()를 이용하여 기준에 따른 데이터 그룹 분할. |
* 데이터의 누락 값 확인. *특정 값을 다른 값으로 대체. |
CSV 파일을 다뤄 보자!
CSV는 테이블 형식의 데이터를 저장하고 이동하는 데 사용되는 구조화된 텍스트 파일 형식이다. CSV는 쉼표로 구분한 변수(Comma Seperated Variables)의 약자로, 이름 그대로 쉼표를 사용하여 데이터를 구분하고 있다. 하지만 쉼표가 아닌 탭이나 콜론, 세미콜론 등의 구분자도 사용할 수 있다. 일반적으로 쉼표를 사용하기 때문에 우리도 이번에는 쉼표를 사용할 것이다.
CSV는 마이크로소프트사의 엑셀과 같은 스프레드 시트(Spread Sheet) 소프트웨어에 적합한 형식이다. 데이터 과학에서 쓰는 데이터 중에는 대다수가 CSV 형식으로 공유된다. (참고로 저번 장에서 살핀 코로나-19 관련 데이터를 제공한 사이트에서도 csv 파일로 데이터를 공유한다.)
CSV파일은 필드를 나타내는 열과 레코드를 나타내는 행으로 구성된다. 만약 데이터 중간에 구분자로 쓰이는 문자가 포함된다면 따옴표를 사용해서 필드를 묶어야 한다. 예로 들어 'Asia, 0.143'이라는 데이터가 있으면 데이터 중간에 쉼표가 구분자로 인식되지 않도록 따옴표로 감싸는 것이다. CSV파일의 첫 번째 레코드에는 열 제목이 포함되어 있을 수 있다. 이건 CSV 형식 파일에서 꼭 있어야 하는 사항은 아니기 때문에 삭제할 수도 있다.
CSV파일의 크기를 알 수 없거나, 가늠할 수 없을만큼 큰 경우에는 한 번에 모든 레코드를 읽지 않는 것이 좋다. 너무 많을 때는 행의 일부만 읽고, 다 읽고 처리한 다음에 삭제하고 다음 행 덩어리를 가져오는 방향으로 해야 할 수도 있다.
파이썬에서 CSV 파일을 다루려면 csv 모듈을 불러와야 한다. 이 csv 모듈에서는 csv.reader()와 csv.writer()를 제공한다. 두 객체 모두 첫 번째 매개변수로 파일 핸들을 사용한다. 필요한 경우에는 delimiter라는 매개변수로 구분자를 제공할 수도 있다.
csv를 다루기 전에 우선 데이터를 하나 가져오자. 기상자료개방 포털 사이트에서 제공한 지난 기상자료를 다운로드하자. 이 책의 github 사이트(https://github.com/dongupak/DataSciPy/blob/master/data/csv/weather.csv) 에 가면 다운받을 수 있다. 2011년 8월 1일부터 2020년 8월 1일, 총 9년 동안 울릉도의 기온, 풍속 데이터를 정리해둔 csv파일이다.
import csv
f = open('D:/weather.csv') #weather.csv가 저장된 경로
data = csv.reader(f) #reader()함수로 읽어온다.
for row in data :
print(row)
f.close()
------------------------------------------------------
['일시', '평균기온', '최대풍속', '평균풍속']
['2010-08-01', '28.7', '8.3', '3.4']
['2010-08-02', '25.2', '8.7', '3.8']
['2010-08-03', '22.1', '6.3', '2.9']
['2010-08-04', '25.3', '6.6', '4.2']
...
|
cs |
CSV 파일에 저장된 데이터를 한 줄 씩 읽으려면 for문을 사용해야 한다. 위의 코드 속 반복문을 실행시키면 아래처럼 각 행의 데이터가 리스트 형식으로 출력된다. 이 weather.csv는 2010년부터 2020년... 그러니까 10년 동안 매일 측정한 데이터를 다루기 때문에 전부 출력하는 데에는 시간이 꽤 걸린다. 그만 출력하고 싶으면 Ctrl + c 를 누르면 멈춘다.
그런데 여기서 ['일시', '평균기온', '최대풍속', '평균풍속'] 처럼 나오는 헤더를 제외한 나머지 데이터만 출력하고 싶으면 어떻게 해야 할까? 간단하다. next() 함수를 이용하면 된다.
import csv
f = open('D:/weather.csv') #weather.csv가 저장된 경로
data = csv.reader(f) #reader()함수로 읽어온다.
header = next(data) #헤더를 제거한다.
for row in data :
print(row)
f.close()
------------------------------------------------------
['2010-08-01', '28.7', '8.3', '3.4']
['2010-08-02', '25.2', '8.7', '3.8']
['2010-08-03', '22.1', '6.3', '2.9']
['2010-08-04', '25.3', '6.6', '4.2']
...
|
cs |
헤더를 제거하는 것 까지는 좋은데, 원하는 열만 뽑아서 출력할 수는 없을까?
왜 없을까! 당연히 가능하다. 이번에도 인덱스를 이용한다. 예를 들어 '평균기온' 데이터만 추출한다고 쳐 보자.
import csv
f = open('D:/weather.csv') #csv 파일 불러오기
data = csv.reader(f) #reader()로 읽기
header = next(data) #헤더 제거
for row in data :
print(row[1], end = ', ') #평균기온 출력, 쉼표로 연결
f.close() #파일은 닫는다.
------------------------------------------------------
28.7, 25.2, 22.1, 25.3, 27.2, 26.8, 27.5, 26.6, 26.9, 25.6,
24.6, 23.7, 24.3, 25, 24.5, 26.2, 23.9, 23.4, 24.3, 25.4,
27.8, 28.3, 28.9, 27.5, 24.7, 25.8, 26.4, 26.7, 27.3, 26.2,
26.6, 27, 25, 24.2, 26.5, 27.2, 23.1, 20.7, 19.7, 22.2, 2
4.8, 22.8, 22.2, 20.1, 20.8, 21.3, 21.9, 22.4, 23.6, 20.7,
20.8, ...
|
cs |
열만 따로 출력하는 것도 이제 알겠다. 그러면 최댓값과 최솟값을 찾으려면 어떻게 하는 게 좋을까?
import csv
f = open('D:/weather.csv')
data = csv.reader(f)
header = next(data)
max_cel = 0.0
min_cel = 0.0
for row in data :
if row[1] == '' : #온도 데이터가 없으면 0
cel = 0
else :
cel = float(row[1]) #있으면 그대로 수치화.
if max_cel < cel : #최고 기온을 갱신한다.
max_cel = cel
if min_cel > cel : #최저 기온을 갱신한다.
min_cel = cel
print('최고 기온 : ', max_cel)
print('최저 기온 : ', min_cel)
f.close()
-----------------------------------------------
최고 기온 : 31.3
최저 기온 : -9.0
|
cs |
위의 코드에서는 max_cel과 min_cel이라는 변수를 0.0으로 초기화한 뒤 이보다 높거나 낮은 row[1]이 발견되면 max_cel과 min_cel을 row[1]의 값으로 갱신시킨다. 그런데 이때 row[1]에 저장된 값은 문자열이기 때문에 숫자로 다루려면 float()함수를 통해 실수로 고치는 과정을 한 번 거쳐줘야 한다. 그리고 혹시나 데이터가 빠져있을 때를 대비해 데이터가 없는 (문자열이 ''인 경우는) cel = 0과 같이 기온을 0으로 처리해야 한다.
데이터 구조: 시리즈와 데이터 프레임
방금 살펴본 csv 모듈 말고도 CSV 데이터를 처리할 수 있는 모듈이 있다. 바로 가장 처음 설명했던 판다스이다.
판다스에서는 데이터 저장을 위한 시리즈와 데이터 프레임의 2가지 데이터 구조를 제공한다. 시리즈는 레이블이 붙은 1차원 벡터이고, 데이터 프레임은 행과 열로 된 2차원 테이블이다. 데이터 프레임의 각 열은 시리즈로 구성된다.
이 데이터 구조는 모두 넘파이 배열을 이용하기 때문에 속도가 빠르고, 항목마다 값의 변경이 가능하며, 시리즈를 빼면 크기 변경도 가능하다. 각 행과 열에는 이름이 부여되고, 행의 이름을 인덱스(Index), 열의 이름을 컬럼스(Columns)라고 부른다.
시리즈(Series)
시리즈(Series)는 동일한 유형의 데이터를 저장하는 1차원 배열이다.
판다스에서 시리즈 데이터를 만들려면 Series 클래스를 이용한다. 리스트를 넘겨주면 받은 리스트로 1차원 벡터 구조의 시리즈 데이터를 만든다.
>>> import numpy as np
>>> import pandas as pd
#nan은 Not a Number의 약자로 수치연산은 가능하지만 정의할 수 없는 값이다.
>>> series = pd.Series([1, 3, 4, np.nan, 6, 8])
>>> series
0 1.0
1 3.0
2 4.0
3 NaN
4 6.0
5 8.0
dtype: float64
|
cs |
이 시리즈가 판다스에서 사용하는 가장 기초적인 데이터 구조이다.
데이터 프레임(DataFrame)
데이터 프레임(DataFrame)은 시리즈 데이터가 여러 개 모여서 2차원 구조를 갖는 것이다. 시리즈가 판다스에서 사용하는 가장 기초적인 데이터 구조라면 데이터 프레임은 판다스가 데이터를 분석할 때 쓰는 가장 기본적인 틀이다.
이름 | 나이 | 성별 | 평점 |
김수안 | 19 | 여 | 4.35 |
김수정 | 23 | 여 | 4.23 |
박동윤 | 22 | 남 | 4.45 |
강이안 | 19 | 여 | 4.37 |
강지안 | 16 | 남 | 4.25 |
위의 표 또한 데이터 프레임이 될 수 있다.
행과 열로 구분되어 있으며, [이름, 나이, 성별, 평점]이라는 레이블이 붙어 있으며, 하나의 행은 여러 종류의 데이터를 담고, 모든 행은 동일한 형태의 자료로 배치된다.
>>> name_series = pd.Series(['김수안', '김수정', '박동윤', '강이안', '강지안'])
>>> age_series = pd.Series([19, 23, 22, 19, 16])
>>> sex_series = pd.Series(['여', '여', '남', '여', '남'])
>>> grade_series = pd.Series([4.35, 4.23, 4.25, 4.37, 4.25])
>>> print(name_series, age_series, sex_series, grade_series)
0 김수안
1 김수정
2 박동윤
3 강이안
4 강지안
dtype: object 0 19
1 23
2 22
3 19
4 16
dtype: int64 0 여
1 여
2 남
3 여
4 남
dtype: object 0 4.35
1 4.23
2 4.25
3 4.37
4 4.25
dtype: float64
|
cs |
우선 이 표를 이름, 나이, 성별, 평점을 나열한 시리즈로 만들어 보았다.
>>> df = pd.DataFrame({'이름' : name_series, '나이' : age_series,
'성별' : sex_series, '평점': grade_series})
>>> print(df)
이름 나이 성별 평점
0 김수안 19 여 4.35
1 김수정 23 여 4.23
2 박동윤 22 남 4.25
3 강이안 19 여 4.37
4 강지안 16 남 4.25
|
cs |
그리고 데이터프레임으로 만들어보았다. 데이터프레임을 만들 때는 DataFrae()함수를 이용하고, 인자로는 딕셔너리 구조를 주어야 한다. 딕셔너리에서의 키(Key)가 시리즈가 차지할 열(Column)의 이름이 되고, 값(Values)은 시리즈가 된다.
데이터프레임에서도 인덱스와 컬럼스 객체를 정의하고 사용하는데, 인덱스는 따로 설정하지 않으면 0부터 n까지의 수로 자동 생성된다. 좀 더 깔끔하게 데이터프레임을 살피고 싶다면 인덱스를 다른 데이터로 지정해주는 것이 좋을 것이다.
>>> df.set_index('이름')
나이 성별 평점
이름
김수안 19 여 4.35
김수정 23 여 4.23
박동윤 22 남 4.25
강이안 19 여 4.37
강지안 16 남 4.25
|
cs |
set_index(key) 함수를 통해 넘겨받은 키를 인덱스로 지정할 수 있다.
판다스로 데이터 파일을 읽고 데이터 프레임을 만들자!
이번에는 판다스를 사용해서 데이터 파일을 읽고 읽은 데이터를 데이터프레임의 형태로 만들어보자.
보통 우리는 데이터프레임을 직접 생성하지 않는다. 일반적으로 이미 모아져 있는 데이터 파일을 찾고 불러와서 사용한다.
이번에 쓸 데이터는 국가에 대한 정보가 저장된 파일, countries.csv이다. 역시 이 책의 github 페이지에서 구할 수 있다.
>>> import pandas as pd
>>> df = pd.read_csv("D:\countries.csv")
>>> df #첫 열은 데이터로 사용려고 인덱스 이름을 설정하지 않았다.
Unnamed: 0 country area capital population
0 KR Korea 98480 Seoul 51780579
1 US USA 9629091 Washington 331002825
2 JP Japan 377835 Tokyo 125960000
3 CN China 9596960 Beijing 1439323688
4 RU Russia 17100000 Moscow 146748600
|
cs |
늘 그렇지만 CSV 파일이 데이터프레임이 되려면 각 행이 같은 구조로 되어 있고, 각 열은 동일한 자료형을 가져야 한다. CSV 파일에 문제가 없다면 에러 없이 위처럼 출력이 잘 될 것이다.
데이터프레임에서는 각 행들이 하나의 객체에 대한 정보를 표시한다. 위의 예시에서는 각 행에 특정 국가의 정보를 표시하고, 각 열들은 서로 다른 속성(Property)을 나타낸다. 여기서는 국가명(country), 면적(area), 수도(capital), 인구(population)이 열의 레이블에 해당한다.
외부에서 불러온 데이터프레임은 불러올 때부터 인덱스를 변경할 수 있다. index_col 이라는 키워드 매개변수를 사용한다.
>>> df = pd.read_csv("D:\countries.csv", index_col = 0) #인덱스를 0열로 설정.
>>> df
country area capital population
KR Korea 98480 Seoul 51780579
US USA 9629091 Washington 331002825
JP Japan 377835 Tokyo 125960000
CN China 9596960 Beijing 1439323688
RU Russia 17100000 Moscow 146748600
|
cs |
방금 코드에서 주석으로 달아놓았듯이 일부러 0열의 인덱스는 비워놓았다. 0열을 인덱스로 주었더니 이렇게 보기 좋은 데이터 프레임이 완성된다.
이 데이터프레임에서 열 단위로 데이터를 가져오고 싶을 때는 어떻게 할까?
리스트에서 인덱싱을 하는 것처럼 대괄호 안에 열의 이름을 넣어주면 된다.
>>> df1 = pd.read_csv("D:\countries.csv")
>>> df2 = pd.read_csv("D:\countries.csv", index_col = 0)
#인덱스를 설정하지 않은 데이터프레임
>>> print(df1['population'])
0 51780579
1 331002825
2 125960000
3 1439323688
4 146748600
Name: population, dtype: int64
#인덱스를 설정한 데이터프레임
>>> print(df2['population'])
KR 51780579
US 331002825
JP 125960000
CN 1439323688
RU 146748600
Name: population, dtype: int64
|
cs |
두 가지 이상의 열을 가져오고 싶으면 열의 리스트를 만들어서 인자로 넘겨주어야 한다.
>>> print(df[ ['area', 'population'] ] )
area population
KR 98480 51780579
US 9629091 331002825
JP 377835 125960000
CN 9596960 1439323688
RU 17100000 146748600
|
cs |
인덱스 지정 여부가 생각보다 꽤 중요하다. 위처럼 한 번 인덱스를 따로 설정해 놓으면 특정 열의 데이터를 가져올 때에도 해당 데이터가 속한 행에 대해 인덱스로 설정한 열의 정보가 출력된다. 위에서는 인덱스를 국가코드로 설정했기 때문에 숫자가 아닌 국가코드가 인덱스로 나타난다.
열이 아닌 행을 가져오는 방법에는 여러가지가 있다.
head(), tail(), 인덱스 슬라이싱, loc[] 를 사용할 수 있다.
(1) head(), tail()
각각 첫 5행과 마지막 5행을 가져오는 함수이다. 인덱스 슬라이싱으로 치면 각각 [0:5], [-5:]와 같은 결과가 나온다.
>>> import pandas as pd
>>> import matplotlib.pyplot as plt
>>> weather = pd.read_csv('D:/weather.csv', index_col = 0, encoding = 'CP949')
>>> weather.tail() # == weather[-5:]
평균기온 최대풍속 평균풍속
일시
2020-07-27 22.1 4.2 1.7
2020-07-28 21.9 4.5 1.6
2020-07-29 21.6 3.2 1.0
2020-07-30 22.9 9.7 2.4
2020-07-31 25.7 4.8 2.5
>>> weather.head() # == weather[0:5]
평균기온 최대풍속 평균풍속
일시
2010-08-01 28.7 8.3 3.4
2010-08-02 25.2 8.7 3.8
2010-08-03 22.1 6.3 2.9
2010-08-04 25.3 6.6 4.2
2010-08-05 27.2 9.1 5.6
|
cs |
(2) 인덱스 슬라이싱
그동안 많이 해 봐서 알 것이다. 데이터프레임 이름[인덱스] 처럼 입력하면 슬라이싱 할 수 있다.
>>> countries_df[:3]
country area capital population
KR Korea 98480 Seoul 51780579
US USA 9629091 Washington 331002825
JP Japan 377835 Tokyo 125960000
>>> weather[:2]
평균기온 최대풍속 평균풍속
일시
2010-08-01 28.7 8.3 3.4
2010-08-02 25.2 8.7 3.8
|
cs |
(3) loc[ ]
행의 레이블, 즉 인덱스를 사용하여 행을 선택할 때에 사용되는 메소드이다. [레이블] 형태의 접근은 열을 선택하는 데에 사용되기 때문에 이 특별한 메소드를 써줘야 한다.
>>> countries_df.loc['KR']
country Korea
area 98480
capital Seoul
population 51780579
Name: KR, dtype: object
>>> weather.loc['2020-06-19']
평균기온 19.1
최대풍속 7.0
평균풍속 3.4
Name: 2020-06-19, dtype: float64
|
cs |
이 loc[ ]과 인덱스 슬라이싱을 동시에 써 주면 특정 범위에 해당하는 열과 행을 가져올 수 있다. 이때 열과 행의 순서는 상관 없다.
>>> countries_df['population'][:3] #['population']과 [:3]의 순서는 상관X.
KR 51780579
US 331002825
JP 125960000
Name: population, dtype: int64
>>> countries_df[:3]['population']
KR 51780579
US 331002825
JP 125960000
Name: population, dtype: int64
>>> countries_df.loc['US', 'capital'] #US 행에서 capital 열에 해당하는 값
'Washington'
>>> countries_df['capital'].loc['US'] #capital 열에서 US 행에 해당하는 값
'Washington'
|
cs |
데이터 프레임과 파이플롯의 합작!
우리는 앞에서 선택한 열을 그래프로 그릴 수 있다. 방법은 간단하다! 열을 지정한 데이터프레임의 이름에 plot() 메소드만 추가해주면 된다.
>>> import pandas as pd
>>> import matplotlib.pyplot as plt
>>> countries_df = pd.read_csv('D:/countries.csv', index_col= 0) #CSV 파일을 읽어온다.
#인구 열을 잘라와서 막대그래프로 그리고 색깔을 파랑, 어두운 주황, 초록, 빨강, 마젠타로 지정한다.
>>> countries_df['population'].plot(kind = 'bar', color = ('b', 'darkorange', 'g', 'r', 'm') )
<AxesSubplot:>
>>> plt.show()
|
cs |
파이 차트를 그리려면 코드를 약간만 수정해주면 된다.
>>> import pandas as pd
>>> import matplotlib.pyplot as plt
>>> countries_df = pd.read_csv('D:/countries.csv', index_col= 0) #CSV 파일을 읽어온다.
#인구 열을 잘라와서 파이 차트를 그린다. (파이 차트는 색을 지정하지 않는다.)
>>> countries_df['population'].plot(kind = 'pie')
<AxesSubplot:ylabel='population'>
>>> plt.show()
|
cs |
판다스로 히스토그램을 그릴 수도 있다. 이번에는 weather.csv를 다시 읽어서 울릉도에 부는 바람이 어느 정도인지 살펴 보자. csv 모듈이 아닌 pandas 모듈에서 읽어올 것이다.
>>> import pandas as pd
>>> import matplotlib.pyplot as plt
#weather.csv를 불러오되 인덱스를 0열로 설정하고 'CP949'로 인코딩하여 한글을 처리한다.
>>> weather = pd.read_csv('D:/weather.csv', index_col = 0, encoding = 'CP949')
>>> weather['평균풍속'].plot(kind = 'hist', bins = 33) #bins는 평균풍속의 최댓값
<AxesSubplot:ylabel='Frequency'>
>>> plt.show()
|
cs |
판다스 모듈에서 csv를 읽어올 때 주의해야 할 점이 하나 있다. csv 파일로 읽어올 때와는 달리 숫자 데이터가 float()함수를 거치지 않아도 알아서 실수 데이터로 읽는다. 그래서 따로 float() 함수를 써서 실수로 바꾸어주지 않아도 된다.
판다스로 새로운 열 삽입하기
판다스는 각 항목 간 연산이 수월하기 때문에 이를 잘 이용하면 다른 열의 정보를 토대로 새로운 열을 생성할 수도 있다. 앞에서 다룬 'countries.csv'의 인구와 면적 값을 이용해서 인구 밀도를 계산해서 새로운 열로 삽입해 보자.
>>> import pandas as pd
>>> import matplotlib.pyplot as plt
>>>
>>> countries_df = pd.read_csv('D:/countries.csv', index_col = 0)
#각 국가별 인구를 면적으로 나눈 값을 'density(밀도)'라는 이름의 열에 저장한다.
>>> countries_df['density'] = countries_df['population'] / countries_df['area']
>>> print(countries_df)
country area capital population density
KR Korea 98480 Seoul 51780579 525.797918
US USA 9629091 Washington 331002825 34.375293
JP Japan 377835 Tokyo 125960000 333.373033
CN China 9596960 Beijing 1439323688 149.977044
RU Russia 17100000 Moscow 146748600 8.581789
|
cs |
이렇게 'density'라는 이름의 열이 새로 추가된 것을 볼 수 있다.
위의 코드에서 알 수 있듯이, 새로운 열을 추가하려면 데이터프레임에 대괄호[ ]를 붙이고 새로 넣을 열의 이름을 써 주면 된다.
판다스로 데이터를 분석해 보자!
이때까지 판다스의 사용법에 대해 알아보았다. 이제 우리는 외부 파일을 읽고 데이터프레임을 생성하여 필요한 행과 열을 선택할 수 있으니 이번에는 데이터를 본격적으로 분석해보려 한다.
>>> import pandas as pd
>>> weather = pd.read_csv('D:/weather.csv', index_col = 0, encoding = 'CP949')
>>> print(weather.describe())
평균기온 최대풍속 평균풍속
count 3653.000000 3649.000000 3647.000000 #데이터 개수
mean 12.942102 7.911099 3.936441 #평균
std 8.538507 3.029862 1.888473 #표준편차
min -9.000000 2.000000 0.200000 #최솟값
25% 5.400000 5.700000 2.500000
50% 13.800000 7.600000 3.600000
75% 20.100000 9.700000 5.000000
max 31.300000 26.000000 14.900000 #최댓값
|
cs |
describe() 함수를 사용하면 평균값(mean), 표준편차(std), 최솟값(min), 최댓값(max) 등을 쉽게 알 수 있다. 이 데이터는 모두 수치 데이터로 처리되기 때문에 모든 열에 대해 분석이 이루어진다. 만약 이 데이터 사이에 문자열이 포함되어 있다면 문자열 데이터가 있는 열은 분석에서 제외된다.
이 descrbie() 함수를 써서 나오는 항목들은 개별로 구할 수도 있다.
>>> weather.mean() #평균값
평균기온 12.942102
최대풍속 7.911099
평균풍속 3.936441
dtype: float64
>>> weather.count() #데이터 개수
평균기온 3653
최대풍속 3649
평균풍속 3647
dtype: int64
|
cs |
개별로 구할 때 역시 특정 열에서의 항목만 구할 수 있다.
>>> weather[['최대풍속', '평균기온']].max()
최대풍속 26.0
평균기온 31.3
dtype: float64
>>> weather[['최대풍속', '평균기온']].count()
최대풍속 3649
평균기온 3653
dtype: int64
|
cs |
특정한 값과 조건에 맞게 골라내자!
데이터를 분석할 때는 특정 데이터 값에 기반해서 그룹으로 묶거나, 어떤 조건에 따라 데이터를 걸러내야 할 때가 있다. 조금 전에 살펴본 월별 풍속 데이터 분석 역시 데이터를 생성된 달에 따라 그룹으로 묶는 일이라 할 수 있다.
이때까지 우리가 배운 것으로 데이터를 그룹화 시키려면 반복문을 사용하여 데이터프레임의 내용을 하나씩 살펴보며 옮겨야 할 공간으로 데이터를 하나씩 옮겨야 한다. 물론 이는 매우 번거로운 작업이고, 판다스에는 이를 대체할 좋은 함수가 있다.
>>> weather = pd.read_csv('D:/weather.csv', encoding = 'CP949')
>>> weather['month'] = pd.DatetimeIndex(weather['일시']).month #일시에서 월만 따로 떼서 month로 저장한다.
>>> means = weather.groupby('month').mean() #month(1, 2, 3, ..., 12) 별 데이터를 그룹화해서 평균을 구한다.
>>> print(means)
평균기온 최대풍속 평균풍속
month
1 1.598387 8.158065 3.757419
2 2.136396 8.225357 3.946786
3 6.250323 8.871935 4.390291
4 11.064667 9.305017 4.622483
5 16.564194 8.548710 4.219355
6 19.616667 6.945667 3.461000
7 23.328387 7.322581 3.877419
8 24.748710 6.853226 3.596129
9 20.323667 6.896333 3.661667
10 15.383871 7.766774 3.961613
11 9.889667 8.013333 3.930667
12 3.753548 8.045484 3.817097
|
cs |
바로 groupby() 함수이다. 이 groupby() 함수의 인자는 우리가 그룹을 묶을 때 쓸 열의 레이블이고, 해당 열에 있는 데이터가 동일하면 하나의 그룹으로 묶인다. 위의 코드에서는 mean() 메소드로 월별 평균을 구해봤지만, sum()을 사용하면 합을 구할 수도 있다.
이번에는 조건에 따라 필터링해보자.
예를 들어 위의 weather.csv에서 초속 10m/s 이상의 강풍이 불었던 날을 찾아보려 한다.
>>> weather['최대풍속'] >= 10.0 #최대풍속이 10.0 이상이면
0 False #그날은 10m/s 이상의 강풍이 분 것.
1 False
2 False
3 False
4 False
...
3648 False
3649 False
3650 False
3651 False
3652 False
Name: 최대풍속, Length: 3653, dtype: bool
>>> weather[ weather['최대풍속'] >= 10.0 ] #'최대풍속'이 10.0 이상이면
일시 평균기온 최대풍속 평균풍속 month
9 2010-08-10 25.6 10.2 5.5 8
12 2010-08-13 24.3 10.9 4.6 8
13 2010-08-14 25.0 10.8 4.4 8
14 2010-08-15 24.5 16.9 10.3 8
29 2010-08-30 26.2 10.5 6.2 8
... ... ... ... ... ...
3622 2020-07-01 16.8 19.7 8.7 7
3632 2020-07-11 20.1 10.3 4.1 7
3634 2020-07-13 17.8 10.3 4.6 7
3635 2020-07-14 17.8 12.7 9.4 7
3641 2020-07-20 23.0 11.2 7.3 7
[830 rows x 5 columns] #그 열의 데이터만 뽑아 새 데이터 프레임을 생성.
|
cs |
결손값을 찾고 삭제하고 메우자!
>>> weather.count()
일시 3653
평균기온 3653
최대풍속 3649 #최대풍속의 개수와
평균풍속 3647 #평균풍속의 데이터가 모자라다.
month 3653
dtype: int64
|
cs |
우리는 조금 전에 describe()로 데이터프레임의 세부사항을 살펴보았고, 그 중에서도 count()로 데이터의 개수를 세 보았다. 그런데 count()에서 나오는 결과를 보면 분명 2010년 8월 1일부터 2020년 8월 1일까지 10년 동안 매일 데이터를 저장했을 텐데 평균기온과 최대풍속, 평균풍속의 데이터 개수가 다르다.
아예 수집하지 않았거나, 측정 장치가 고장났거나... 이유야 어찌 되었건, 데이터에 값이 입력되지 못할 때가 있다. 그러면 그 항목이 비게 되고, 이를 결손값(Missing Data)이라 한다. '최대풍속' 열에는 '평균기온'에 비해 4개의 결손값이 더 있는 것이고, '평균풍속' 열에는 '평균기온'에 비해 6개의 결손값이 있는 것이다. 이 결손값을 판다스에서는 NaN 또는 NA로 표기한다.
>>> import pandas as pd
>>> weather = pd.read_csv('D:/weather.csv', index_col = 0, encoding = 'CP949')
#결손값 유무를 검사하는 함수: isna()
>>> missing_data = weather [ weather['평균풍속'].isna() ] #결손값이 있는 값을 저장한다.
>>> print(missing_data)
평균기온 최대풍속 평균풍속
일시
2012-02-11 -0.7 NaN NaN
2012-02-12 0.4 NaN NaN
2012-02-13 4.0 NaN NaN
2015-03-22 10.1 11.6 NaN
2015-04-01 7.3 12.1 NaN
2019-04-18 15.7 11.7 NaN
|
cs |
결손값이 있는지 검사하려면 isna() 함수를 사용한다. 이 함수는 각 항목 별로 결손값이 있으면 True를, 없으면 False를 반환한다. 그래서 위의 코드에서는 이를 응용하여 결손값이 있었던 날만 추출해보았다. weather [ weather['평균풍속'].isna() ]를 사용하면 값이 없는 날만 뽑아온다.
그래서 이 결손값이 있는 데이터는 어떻게 처리해야 하냐고 묻는다면, 삭제하거나 메우는 수밖에 없다고 답할 수 있다.
우선 삭제하려면 이 함수를 사용한다.
#axis = 0 : 결손O 행 삭제, 1이면 열을 삭제.
#how = 'any' : 결손 하나라도 있으면 통째로 삭제, 'all'이면 전부 결손이어야 삭제.
#inplace = False : 원본은 두고 고쳐진 데이터프레임 반환, True면 원본에서 삭제 후 반환.
>>> weather.dropna(axis = 0, how = 'any', inplace = False)
평균기온 최대풍속 평균풍속
일시
2010-08-01 28.7 8.3 3.4
2010-08-02 25.2 8.7 3.8
2010-08-03 22.1 6.3 2.9
2010-08-04 25.3 6.6 4.2
2010-08-05 27.2 9.1 5.6
... ... ... ...
2020-07-27 22.1 4.2 1.7
2020-07-28 21.9 4.5 1.6
2020-07-29 21.6 3.2 1.0
2020-07-30 22.9 9.7 2.4
2020-07-31 25.7 4.8 2.5
[3646 rows x 3 columns]
|
cs |
dropna() 함수이다. dropna()의 매개변수 중에서 axis, how, inplace 항목에 주목하자.
axis는 삭제할 범위(0: 행, 1: 열)를 결정하고, how는 삭제할 조건('any': 하나라도 있을 때, 'all': 전부 결손일 때), inplace는 원본을 보존할지에 대한 여부(False: 원본과는 별개로 두고 고쳐서 반환, True: 원본을 고쳐서 반환)를 결정한다.
그리고 결손값을 메우려면 이 함수를 사용한다.
>>> weather = pd.read_csv('D:/weather.csv', index_col = 0, encoding = 'CP949')
#결손값 확인
>>> missing_data = weather[ weather['평균풍속'].isna() ]
>>> missing_data
평균기온 최대풍속 평균풍속
일시
2012-02-11 -0.7 NaN NaN
2012-02-12 0.4 NaN NaN
2012-02-13 4.0 NaN NaN
2015-03-22 10.1 11.6 NaN
2015-04-01 7.3 12.1 NaN
2019-04-18 15.7 11.7 NaN
#fillna() 함수로 결손값을 0으로 채운다.
>>> weather.fillna(0, inplace = True)
#결손이었던 12년도 2월 11일 데이터가 0으로 채워짐.
>>> print(weather.loc['2012-02-11'])
평균기온 -0.7
최대풍속 0.0
평균풍속 0.0
Name: 2012-02-11, dtype: float64
|
cs |
fillna() 함수를 이용한다. fillna()의 괄호 속의 첫 인자가 NaN 자리를 메꾸어 준다. 위의 코드에서는 첫 인자로 0을 주었기 때문에 원래 결손값이 2개나 있던 12년도 2월 11일 데이터가 0이라는 값으로 채워진 것을 볼 수 있다.
데이터의 구조를 바꾸고 합쳐보자!
이때까지 데이터프레임을 CSV파일을 읽어서 생성했지만, 꼭 CSV파일이 아니어도 데이터프레임을 만들 수 있다. 우리가 가장 초반에 직접 입력해서 데이터프레임을 만들었듯이 딕셔너리 데이터를 이용할 수 있다. 딕셔너리의 키가 열의 레이블이 되고, 딕셔너리의 키에 해당하는 값이 열을 채우는 데이터 리스트로 들어간다.
import pandas as pd
df_1 = pd.DataFrame({'item' : ['ring0', 'ring0', 'ring1', 'ring1'],
'type' : ['Gold', 'Silver', 'Gold', 'Bronze'],
'price': [20000, 10000, 50000, 30000]})
print(df_1)
-----------------------------------------------------------------
item type price
0 ring0 Gold 20000
1 ring0 Silver 10000
2 ring1 Gold 50000
3 ring1 Bronze 30000
|
cs |
이렇게 딕셔너리로 데이터프레임을 만들었다.
데이터 분석을 하다 보면 테이블에서 행과 열의 위치를 바꾸거나, 어떤 기준에 따라 집계하여 테이블의 구조를 변경해야 하는 경우가 종종 있다. 판다스에서는 이 데이터프레임의 구조를 변경하는 함수를 제공한다. 많은 종류가 있지만 이번에는 pivot() 함수를 살펴보려 한다.
df_2 = df_1.pivot(index = 'item', columns = 'type', values = 'price')
print(df_2)
-------------------------
type Bronze Gold Silver
item
ring0 NaN 20000.0 10000.0
ring1 30000.0 50000.0 NaN
|
cs |
위의 예시에서는 item을 인덱스로 하고, type을 열의 레이블로 사용하여 2행 4열의 형태로 바꾸었다. 표로 바꾸어보자면 이런 느낌이다.
일반적으로 데이터는 하나의 큰 테이블로 저장도지 않고 작은 테이블로 나누어져 있는 경우가 많다. 저장과 관리의 편리성 문제도 있고, 데이터 수집 시기나 주체 등이 달라 별도로 생성된 경우가 많다.
이런 데이터 프레임을 합치는 함수는 두 가지가 있다.
우선 함수를 살펴보기 전에 데이터 프레임 두 개를 먼저 생성하고 시작하자.
import pandas as pd
df_1 = pd.DataFrame( {'A' : ['a10', 'a11', 'a12'],
'B' : ['b10', 'b11', 'b12'],
'C' : ['c10', 'c11', 'c12']} , index = ['가', '나', '다'] )
df_2 = pd.DataFrame( {'B' : ['b23', 'b24', 'b25'],
'C' : ['c23', 'c24', 'c25'],
'D' : ['d23', 'd24', 'd25']} , index = ['다', '라', '마'] )
|
cs |
(1) concat() 함수
df_3 = pd.concat( [df_1, df_2]) #두 데이터프레임을 합친다.
print(df_3)
-----------------------------
A B C D
가 a10 b10 c10 NaN
나 a11 b11 c11 NaN
다 a12 b12 c12 NaN
다 NaN b23 c23 d23
라 NaN b24 c24 d24
마 NaN b25 c25 d25
|
cs |
concat() 함수에는 합칠 데이터 프레임의 리스트와 axis, join 매개변수가 필요하다. 위의 예시에서는 axis와 join 매개변수를 생략했지만, 우선 설명하자면 아래와 같다.
* axis : 0이면 행을 늘려 붙이고, 1이면 열을 늘려 붙인다. 위의 예시에서는 따로 설정하지 않아서 0이고, 행이 늘었다.
* join : 테이블을 붙일때 레이블을 사용할 방법을 결정한다. 'outer'의 경우 레이블의 합집합으로 생성하고, 'inner'의 경우 레이블의 교집합으로 생성된다. 위의 예시코드 속에서는 디폴트로 outer가 설정되어 있고, A와 D의 값이 없는 부분에는 NaN이 출력되고, 만약 join 인자를 'inner'로 줄 경우 B와 C의 열만 나온다.
(2) join() 함수
>>> df_1.merge(df_2, how = 'outer', on = 'B')
# DataFrame.merge(right, how = 'inner', on = None)
# how : 결합 방식 (left, right, outer inner)
# on : 합칠 부분 (합칠 데이터프레임 모두에 존재하는 열)
----------------------------------------
A B C_x C_y D
0 a10 b10 c10 NaN NaN
1 a11 b11 c11 NaN NaN
2 a12 b12 c12 NaN NaN
3 NaN b23 NaN c23 d23
4 NaN b24 NaN c24 d24
5 NaN b25 NaN c25 d25
|
cs |
이 merge() 함수가 사용하는 연산은 조인(Join)이다.
위의 코드에서는 조인 연산을 'B' 레이블을 가진 열의 데이터가 키가 되도록 해서 진행했다.
이때 두 테이블에 B 말고 C도 동시에 존재하므로 왼쪽(df_1) 프레임의 C는 C_x로, 오른쪽(df_2) 프레임의 C는 C_y라는 이름을 새로 붙여서 합치게 된다.
이 merge() 함수의 동작 결과를 살펴보면 두 데이터를 결합할 때 사용할 키가 될 레이블을 지정하면 그 레이블에 있는 값을 이용해서 테이블을 생성한다. 하지만 우리가 따로 설정해뒀던 '가, 나, 다', '다, 라, 마' 인덱스는 사라져 있는 것을 볼 수 있다. 이렇게 설정해둔 인덱스를 보존하고 이 인덱스를 키로 사용하게 하려면 위의 코드를 이렇게 수정하면 된다.
#left(right)_index = True를 통해 인덱스를 살려두고 키로 설정한다.
df_3 = df_1.merge(df_2, how = 'outer', left_index = True, right_index = True)
print(df_3)
-----------------------
A B_x C_x B_y C_y D
가 a10 b10 c10 NaN NaN NaN
나 a11 b11 c11 NaN NaN NaN
다 a12 b12 c12 b23 c23 d23
라 NaN NaN NaN b24 c24 d24
마 NaN NaN NaN b25 c25 d25
|
cs |
left(또는 right)_index = True 를 입력함으로써 인덱스가 병합의 키가 되도록 한다.
마무리
이번 시간에는 테이블 형태의 데이터를 빠르게 다룰 수 있는 판다스와 csv 모듈에 대해 살펴보았다.
마지막으로 심화문제 12.3을 풀어보고 마치도록 하자. 접은글 속 해답코드는 참고만 하도록 하자.
12.3 : 다음은 P와 Q 자동차 회사의 차종별 마력, 총중량, 그리고 연비를 나타낸 표이다.
P 회사 | A | B | C | D | E | F | G |
마력 | 130 | 250 | 190 | 300 | 210 | 220 | 170 |
총중량 | 1900 | 2600 | 2200 | 2900 | 2400 | 2300 | 2200 |
연비 | 16.3 | 10.2 | 11.1 | 7.1 | 12.1 | 13.2 | 14.2 |
Q 회사 | A | B | C | D |
마력 | 120 | 220 | 120 | 200 |
총중량 | 1900 | 2100 | 1500 | 2900 |
연비 | 18.3 | 19.2 | 21.1 | 17.3 |
(1) 이 표를 판다스 데이터프레임으로 만들어 출력해 보라.
#P사의 차
horse power weight efficiency
name
A 130 1.9 16.3
B 250 2.6 10.2
C 190 2.2 11.1
D 300 2.9 7.1
E 210 2.4 12.1
F 220 2.3 13.2
G 170 2.2 14.2
#Q사의 차
horse power weight efficiency
name
A 120 1.9 18.3
B 220 2.1 19.2
C 120 1.5 21.1
D 200 2.9 17.3
|
cs |
import pandas as pd
dfP = pd.DataFrame({
'name' : ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
'horse power' : [130, 250, 190, 300, 210, 220, 170],
'weight' : [1.9 ,2.6, 2.2, 2.9, 2.4, 2.3, 2.2],
'efficiency' : [16.3, 10.2, 11.1, 7.1, 12.1, 13.2, 14.2] })
dfQ = pd.DataFrame({
'name' : ['A', 'B', 'C', 'D'],
'horse power' : [120, 220, 120, 200],
'weight' : [1.9, 2.1, 1.5, 2.9],
'efficiency' : [18.3, 19.2, 21.1, 17.3]})
dfP, dfQ = dfP.set_index('name'), dfQ.set_index('name')
print(dfP, '\n\n', dfQ)
|
cs |
(2) Q사의 데이터와 P사의 데이터를 합쳐 다음과 같은 하나의 데이터프레임을 만들라.
horse power weight efficiency
name
A 130 1.9 16.3
B 250 2.6 10.2
C 190 2.2 11.1
D 300 2.9 7.1
E 210 2.4 12.1
F 220 2.3 13.2
G 170 2.2 14.2
A 120 1.9 18.3
B 220 2.1 19.2
C 120 1.5 21.1
D 200 2.9 17.3
|
cs |
import pandas as pd
dfP = pd.DataFrame({
'name' : ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
'horse power' : [130, 250, 190, 300, 210, 220, 170],
'weight' : [1.9 ,2.6, 2.2, 2.9, 2.4, 2.3, 2.2],
'efficiency' : [16.3, 10.2, 11.1, 7.1, 12.1, 13.2, 14.2] })
dfQ = pd.DataFrame({
'name' : ['A', 'B', 'C', 'D'],
'horse power' : [120, 220, 120, 200],
'weight' : [1.9, 2.1, 1.5, 2.9],
'efficiency' : [18.3, 19.2, 21.1, 17.3]})
dfPQ = pd.concat( [df_1, df_2])
Df = dfPQ.set_index('name')
print(Df)
|
cs |
(3) 각 차량의 제조사를 알 수 있게 다음과 같이 데이터프레임을 다시 만들어 보라.
horse power weight efficiency com
name
A 130 1.9 16.3 P
B 250 2.6 10.2 P
C 190 2.2 11.1 P
D 300 2.9 7.1 P
E 210 2.4 12.1 P
F 220 2.3 13.2 P
G 170 2.2 14.2 P
A 120 1.9 18.3 P
B 220 2.1 19.2 Q
C 120 1.5 21.1 Q
D 200 2.9 17.3 Q
|
cs |
#앞의 데이터프레임 코드는 생략한다.
dfPQ = pd.concat( [dfP, dfQ])
com = []
for i in range(len(dfPQ['name'])) :
if i <= 7 :
com.append('P')
elif i > 7 :
com.append('Q')
dfPQ['com'] = com
Df = dfPQ.set_index('name')
print(Df)
|
cs |
(4) 각 회사에서 생산하는 차량들의 마력 * 연비의 평균값을 구해 회사별로 출력해 보라.
com
P 2395.285714
Q 3103.000000
|
cs |
'따라하며 배우는 파이썬과 데이터 과학 > PART 2. 데이터 과학과 인공지능' 카테고리의 다른 글
Chapter 14. 기계학습으로 똑똑한 컴퓨터를 만들자 (0) | 2021.06.19 |
---|---|
Chapter 13. 시각 정보를 다루어보자 (0) | 2021.06.18 |
Chapter 11. 차트를 멋지게 그려보자 (0) | 2021.06.15 |
Chapter 10. 넘파이로 수치 데이터를 처리해보자 (1) | 2021.06.15 |
Chapter 09. 텍스트를 처리해보자 (0) | 2021.06.14 |