5장. 판다스와 데이터 분석¶
판다스의 벡터화를 이용 - 코드를 배열 기반으로 간결하게 작성할 수 있다.
데이터 정렬 - 여러 개의 데이터 집합을 다룰 때 데이터 불일치가 생기지 않도록 한다.
5.1 데이터프레임과 시리즈¶
데이터프레임 - 2차원 넘파이 배열 + 열과 행에 레이블이 있고, 각 열에 다른 형태의 데이터 타입을 저장할 수 있다.
시리즈 - 데이터프레임에서 한 행 혹은 한 열을 떼어 내면 1차원 시리즈가 되는데, 여기에 레이블이 붙어 있는 것이라 할 수 있다.
그러므로, 데이터프레임은 파이썬 기반 스프레드시트라 할 수 있다.
# pd.read_excel() 사용하여 엑셀 파일 읽어 들이기
# python >= 3.9, pandas >= 1.2이어야 xlsx를 읽어 들인다.
import pandas as pd
pd.read_excel('../xl/course_participants.xlsx')
- GOTO: 판다스를 이용한 상세한 파일 읽기와 쓰기는 Chapter 7
# 위의 엑셀 파일에서 읽어 들인 데이터프레임을 바닥부터 데이터프레임으로 만들기
# 행렬을 전달해야 하니 중첩 리스트의 형태로 만들자
data = [["Mark", 55, "Italy", 4.5, "Europe"],
["John", 33, "USA", 6.7, "America"],
["Tim", 41, "USA", 3.9, "America"],
["Jenny", 12, "Germany", 9.0, "Europe"]]
df = pd.DataFrame(data=data,
columns=["name", "age", "country", "score", "continent"],
index=[1001, 1000, 1002, 1003])
df
df.info()
df.dtypes
5.1.1 인덱스¶
행의 레이블
인덱스를 통해 더 빨리 검색할 수 있고, 데이터프레임 두 개를 조합하는 등의 동작에 필요하다.
df.index
df.index.name = "user_id"
df
데이터프레임의 인덱스는 중복이 허용되지만 값을 검색하는 속도가 느려질 수 있다.
인덱스 -> 일반적인 열로 바꾸기: reset_index()
새로운 인덱스 열로 만들기: set_index()
즉, 인덱스가 아닌 열을 새로 인덱스로 설정할 때는 먼저 기존 인덱스를 reset_index()
해야 한다.
df.reset_index()
df.reset_index().set_index("name")
NOTE: 데이터프레임 메서드는 사본을 반환한다. 그러므로 원래 데이터프레임은 그대로 유지된다.
# 인덱스를 바꿀 때는 reindex() 메서드를 사용
df.reindex([999, 1000, 1001, 1004])
reindex()
메서드는 새로운 인덱스에 일치하는 행을 모두 찾고 정보가 없는 행은 NaN으로 채움sort_index()
메서드는 인덱스를 정렬할 때 사용sort_values()
메서드는 열을 기준으로 정렬할 때 사용
df.sort_index()
df.sort_values(["continent", "age"])
5.1.2 열¶
df.columns.name
열 헤더 이름 붙이기df.rename()
열 (혹은 행) 이름 바꾸기df.drop()
열 (혹은 행) 제거하기df.T
(transpose) 열과 행을 바꾸기df.loc[]
열의 순서를 바꾸기
df.columns
df.columns.name = "Properties"
df
df.rename(columns={"name": "First name", "age": "Age"})
df.drop(columns=["name", "country"],
index=[1000, 1003])
df.T
df.loc[:, ["continent", "country", "name", "age", "score"]]
5.2 데이터 조작¶
5.2.1 데이터 선택¶
레이블로 선택¶
loc
속성- df.loc[row_selection, column_selection]
슬라이스에서 끝에 값을 포함한다.
열 헤더는 데이터프레임에만 있다. 열 하나를 시리즈로 선택하면 열 헤더가 그 시리즈의 이름이 된다.
위치로 선택¶
넘파이 배열에서 부분 집합을 선택할 때 정수로 된 인덱스를 전달한 것과 같다.
iloc
속성- df.iloc[row_selection, column_selection]
불리언 인덱스를 통한 선택¶
True, False만 포함된 시리즈나 데이터프레임을 통해 데이터프레임에서 부분 집합을 선택하는 방법
데이터프레임의 행을 필터링하는 용도로 엑셀의 자동 필터 기능을 생각해 보자.
# True/False로 이루어진 시리즈
tf = (df["age"] > 40) & (df["country"] == "USA")
tf
df.loc[tf, :]
1) pandas에서는 불리언 연산자인 and/or/not 키워드 대신 &/|/~를 써야 한다. 2) 연산 우선 순위가 꼬일 수 있으니 괄호를 적절히 배치해서 내가 생각한 대로 연산이 될 수 있도록 하자.
df.index
: 인덱스에 필터를 적용isin
: in 연산자와 같은 동작
# 인덱스에 필터를 적용
df.loc[df.index > 1001, :]
# isin으로 행 선택하기
df.loc[df["country"].isin(["Italy", "Germany"]), :]
데이터프레임이 숫자로만 이루어진 경우, 불리언 데이터프레임을 인자로 전달하면,
False 값에 대응하는 값이 NaN으로 변환된 데이터프레임이 반환된다.
이상치같은 특정 값을 제외할 때 가장 널리 쓰인다.
# rainfall이라는 데이터프레임을 만들어 보자
rainfall = pd.DataFrame(data={"City 1": [300.1, 100.2],
"City 2": [400.3, 300.4],
"City 3": [1000.5, 1100.6]})
rainfall
# boolean_df
rainfall < 400
# df[boolean_df]
rainfall[rainfall < 400]
다중 인덱스를 사용한 선택¶
다중 인덱스를 사용하면 데이터를 계층 구조로 묶을 수 있고 부분 집합에 접근하는 것도 쉽다.
# SNIPPET: 다중 인덱스를 만들고 접근하기
# 다중 인덱스는 정렬부터 해야 한다.
df_multi = df.reset_index().set_index(["continent", "country"])
df_multi = df_multi.sort_index()
df_multi
df_multi.loc["Europe", :]
- 인덱스 레벨 여러 개를 적용해서 선택할 때 튜플을 전달
- 다중 인덱스의 일부만 리셋하고자 하면 그 레벨을 인자로 전달
groupby
연산은 다중 인덱스가 포함된 데이터프레임을 반환
df_multi.loc[("Europe", "Italy"), :]
df_multi.reset_index(level=0)
# 일단 원래 데이터프레임을 보존하고 사본을 만들자.
df2 = df.copy()
# boolean_df가 True인 행의 열을 선택하여 일괄적으로 값을 채워넣기
tf = (df2["age"] < 20) | (df2["country"] == "USA")
df2.loc[tf, "name"] = "xxx"
df2
# 값을 특정 열이 아닌 데이터프레임 전체에 일괄적으로 교환하여 채워 넣고자 할 때 사용
rainfall2 = rainfall.copy()
rainfall2[rainfall2 < 400] = 0
rainfall2
값 교체¶
replace
: 앞의 경우 boolean 연산이나 비교 연산과 같은 조건이 필요했지만, 조건 없이 데이터프레임 전체에서 값을 교체하고자 할 때
df2.replace("USA", "U.S.")
# 특정 열에서 교체하고 싶다면
df2.replace({"country": {"USA": "U.S."}})
새로운 열 추가¶
새로운 열 이름에 값을 할당하면 된다.
df2.loc[:, "discount"] = 0
df2.loc[:, "price"] = [49.9, 49.9, 49.9, 49.9]
df2
# 벡터화된 계산도 가능하다.
df2 = df.copy()
df2.loc[:, "birth year"] = 2022 - df2["age"]
df2
5.2.3 누락된 데이터¶
판다스는 넘파이의 np.nan
으로 빈 값을 나타내며 우리 눈에 보이는 출력으로 NaN
으로 나타낸다.
텍스트는 None
, 타임스탬프는 pd.NaT
으로 나타낸다.
# 기존 df 데이터프레임의 사본으로 시작
df2 = df.copy()
df2.loc[1000, "score"] = None # 숫자형이므로 NaN
df2.loc[1003, :] = None
df2
# 누락된 데이터가 포함된 행 제거 - 하나라도 NaN/None이 있으면 제거한다.
df2.dropna()
# 모든 값이 NaN/None이어야만 제거하겠다.
df2.dropna(how="all")
# NaN/None이 섞여 있나?
df2.isna()
# NaN을 그 열의 평균 값으로 채워 넣겠다.
df2.fillna({"score": df2["score"].mean()})
5.2.4 중복 데이터¶
drop_duplicate
메서드: 중복된 행을 제거하고 싶다.is_unique
,unique()
: 특정 열에서 고유한 값만 찾고 싶다.
df.drop_duplicates(["country", "continent"])
df["country"].is_unique
df["country"].unique()
# 기본적으로 중복만 True로 표시 (즉, 첫 행은 True로 표시되지 않음)
df["country"].duplicated()
# duplicated에 keep=False 인자를 전달하면 중복되는 모든 행 표시
df.loc[df["country"].duplicated(keep=False), :]
5.2.5 산술 연산¶
데이터프레임을 합하는 연산을 하였을 때, 자동으로 행과 열의 인덱스에 따라 데이터프레임을 재구성하게 된다.
# Example
more_rainfall = pd.DataFrame(data=[[100, 200], [300, 400]],
index=[1, 2],
columns=["City 1", "City 4"])
rainfall + more_rainfall
# 엑셀과 비슷하게 나오게 만드려면
rainfall.add(more_rainfall, fill_value=0)
# 연산에 참여하지 않은 값은 그대로 출력된다.
# 데이터프레임과 시리즈의 연산에서 시리즈는 인덱스 방향으로 확장된다. (= 모든 열 방향)
# 행에서 추출한 시리즈
rainfall.loc[1, :]
rainfall + rainfall.loc[1, :]
# 열에서 추출한 시리즈
rainfall.loc[:, "City 2"]
# 모든 행 방향으로 각 요소와 더하기
rainfall.add(rainfall.loc[:, "City 2"], axis=0)
5.2.6 텍스트 열 조작¶
# 새로운 데이터프레임 만들기
users = pd.DataFrame(data=[" mArk ", "JOHN ", "Tim", " jenny"],
columns=["name"])
users
users_cleaned = users.loc[:, "name"].str.strip().str.capitalize()
users_cleaned
# J로 시작하는 이름을 찾고 싶다.
users_cleaned.str.startswith("J")
rainfall.applymap(lambda x: f"{x:,.2f}")
5.2.8 뷰와 사본¶
loc
이나 iloc
이 뷰를 반환할까 사본을 반환할까? 쉽게 예측하기 어렵다.
그러므로, 값을 설정할 때 슬라이스로 추출한 데이터프레임이 아닌 원본 데이터프레임을 사용한다.
또, 슬라이스한 부분 집합을 독립적인 데이터프레임으로 유지하고 싶다면 명시적으로 복사해라.
그 외, df.dropna()
, df.sort_values()
같은 데이터프레임 메서드는 항상 사본을 반환한다.
# 새 데이터프레임 만들기
data = [[15, "France", 4.1, "Becky"],
[44, "Canada", 6.1, "Leanne"]]
more_users = pd.DataFrame(data=data,
columns=["age", "country", "score", "name"],
index=[1000, 1011])
more_users
pd.concat([df, more_users], axis=0)
# 모든 열 방향으로 합치기 (axis=1)
data = [[3, 4],
[5, 6]]
more_categories = pd.DataFrame(data=data,
columns=["quizzes", "logins"],
index=[1000, 2000])
pd.concat([df, more_categories], axis=1)
5.3.2 조인과 병합¶
각 데이터프레임의 열을 조합해 새로운 데이터프레임을 만드는 작업으로, 인덱스를 기준으로 행을 결정한다.
Refer pandas 공식 문서의 concat
, join
, merge
참조 https://oreil.ly/OZ4WV
5.4 기술 통계와 데이터 수집¶
groupby
연산과pivot_table
함수에 대해 알아보자.
5.4.1 기술 통계¶
양적 분석을 통해 데이터 집합을 요약 - sum
, mean
, count
같은 메서드를 이용
Refer: 전체 기술 통계 메서드의 리스트 https://oreil.ly/t2q9Q
기본적으로 열 기준으로 통계를 반환
행 통계를 원한다면 axis
인자를 사용 (별 의미가 없다.)
기본적으로 누락된 값은 제외 (엑셀의 AVERAGE와 결과가 같다.)
5.4.2 그룹화¶
Example 1: 대륙별로 그룹을 짓고 (groupby
연산), 각 그룹의 평균 (mean
메서드)을 구하자.
Example 2: 열을 두 개 이상 선택하면 다중 인덱스로 출력
Example 3: 사용자가 직접 만든 함수 (람다 표현식 전달)는 agg()
메서드를 통해 구할 수 있다.
# Example 1
df.groupby(["continent"]).mean()
# Example 2 - 다중 인덱스
df.groupby(["continent", "country"]).mean()
# Example 3 - agg 메서드의 사용
df.loc[:, ["age", "score", "continent"]].groupby(["continent"]).agg(lambda x: x.max() - x.min())
5.4.3 피벗과 해제¶
pivot_table()
함수를 사용
data = [["Oranges", "North", 12.30],
["Apples", "South", 10.55],
["Oranges", "South", 22.00],
["Bananas", "South", 5.90],
["Bananas", "North", 31.30],
["Oranges", "North", 13.10]]
sales = pd.DataFrame(data=data,
columns=["Fruits", "Region", "Revenue"])
sales
# SNIPPET: pivot_table()
pivot = pd.pivot_table(sales,
index="Fruits", columns="Region",
values="Revenue", aggfunc="sum",
margins=True, margins_name="Total")
pivot
# SNIPPET: melt()
# 열의 header를 단일 열로 바꾸는 melt - pivot_table의 역이라고 할 수 있다.
pd.melt(pivot.iloc[:-1, :-1].reset_index(),
id_vars="Fruits",
value_vars=["North", "South"], value_name="Revenue")
%matplotlib inline
import numpy as np
data = pd.DataFrame(data=np.random.randn(4, 4) * 100000,
index=["Q1", "Q2", "Q3", "Q4"],
columns=["East", "West", "North", "South"])
data.index.name = "Quarters"
data.columns.name = "Region"
data
data.plot()
5.5.2 플로틀리¶
확대/축소, 범례를 클릭해서 카테고리를 선택/선택 해제, 마우스 오버 툴팁 등 활용도가 좋다.
처음에 할 일은 노트북의 그래프 서버를 플로틀리로 설정하는 것
pd.options.plotting.backend = "plotly"
data.plot()
# 같은 데이터를 막대 그래프로 그리기
data.plot.bar(barmode="group")
Refer: plotly 문서의 그래프 메서드의 인자 https://oreil.ly/Ekurd
Refer: pandas 시각화 문서 https://oreil.ly/FxYg9
그 외 그래프 라이브러리
5.6 데이터프레임 임포트와 익스포트¶
5.6.1 CSV 파일로 익스포트¶
to_csv()
메서드를 사용
다른 디렉터리로 저장하고 싶다면, macOS는 문자열로 전달, 윈도우는 원시 문자열 (앞에 r
을 붙여)로 전달
5.6.2 CSV 파일 임포트¶
read_csv()
함수에 파일의 경로 전달
sep
인자 등을 같이 설정해야 될 때가 많다. https://oreil.ly/2GMhW
info
, head
/tail
, describe
메서드를 이용해서 잘 불러 들여졌는지 처음과 마지막 행을 훑어 보기 등을 한다.
msft = pd.read_csv('../csv/MSFT.csv')
msft.info()
msft.head()
msft.tail()
msft.describe()
5.7 요약¶
'Prev Contents > Automation tools' 카테고리의 다른 글
[엑셀이 편해지는 파이썬] 7장. 판다스를 사용한 엑셀 파일 조작 (197~210p) (0) | 2022.08.19 |
---|---|
[엑셀이 편해지는 파이썬] 6장. 판다스와 시계열 분석 (175~193p) (0) | 2022.08.18 |
[엑셀이 편해지는 파이썬] 4장. 넘파이 기초 (113~122p) (0) | 2022.08.15 |
[엑셀이 편해지는 파이썬] 3장. 파이썬 시작하기 (71~110p) (0) | 2022.08.15 |
[엑셀이 편해지는 파이썬] 2장. 개발 환경 (43~69p) (0) | 2022.08.15 |