(전공복습) 데이터과학 4. 시각화
차례
이 글은 컴퓨터학과 이중전공생으로서 배운 것들을 다시 한번 정리하고자 남기는 글입니다. 불완전한 기억 등의 이유로 오류가 있을 수 있으며, 참조 내지 이정표로만 사용해주세요.
본 게시글은 고려대학교의 데이터과학 강의를 기반으로 작성자의 추가적인 설명이 덧붙여진 글입니다.
파이썬(pandas
)이나 R을 다루다 보면 데이터프레임이라는 말을 들을 수 있다. 간략하게 말하자면, 데이터프레임은 표를 의미한다. 2차원 자료구조로서 행(Row, 가로줄)과 열(Column, 세로줄)을 가지고 있고, 일반적으로 각 열은 피처(Feature)에 해당하며, 행은 개별 데이터 포인트(Data Point)에 해당한다.
또한, 데이터프레임에는 행과 열에 대한 레이블이 존재한다. 이 레이블들은 각 행과 열을 대표하는 이름을 지닌다. 추가로, 각 열은 도메인(Domain, 정의역)을 가질 수 있는데, 이는 피처의 가능한 값에 대한 정의라고 할 수 있고, 더 간단하게는 그냥 그 열의 타입(Type, 자료형)이라고 생각할 수 있다.
위 이미지는 파이썬 데이터프레임의 예시이다. 우리는 위 데이터프레임을 보고 아래와 같은 사실들을 알 수 있다.
win
또는 loss
이다.그렇다면 pandas
에서 구현된 데이터프레임은 어떤 형태일까? 조금 전에 살펴본 예시가 바로 파이썬 pandas
에서의 데이터프레임이다.
위에서 이야기한 것처럼, 행과 열로 구성된 2차원 자료구조로서, 열 레이블(columns
)과 행 레이블(index
), 값(values
)으로 구성된다. 실질적인 데이터는 값들이고, 나머지는 열과 행을 위한 식별자라고 생각할 수 있다. 또한, 각 열은 dtype
, 즉 타입을 가지며 이것이 도메인의 역할을 하게 된다.
(다만, 레이블들이 고유할 필요는 없다. 그러나 일반적으로는 이를 고유하게 구성하는 것이 권장된다. 특히, 열 레이블에서 더욱 그렇다.)
또한 pandas
에는 시리즈(Series)라는 자료구조가 존재한다. 열과 행 레이블을 가진 2차원 자료구조인 데이터프레임과 달리, 시리즈는 1차원 자료구조를 의미한다. 행을 추출하든, 열을 추출하든, 데이터프레임에서 한 줄을 추출해내면 그 결과가 시리즈이다. 다만, 하나의 열을 추출하는 것이 더 일반적이다.
시리즈는 데이터프레임의 행 레이블(인덱스)과 유사하게, 각 항목별로 인덱스를 가지고 있고, 그 시리즈의 이름(name
)과 타입(dtype
)을 갖는다.
pandas
에서 각 행의 레이블은 인덱스(Index)라 불린다. pandas
의 인덱스는 파이썬의 다른 자료구조들이 갖는 인덱스와 닮은 부분이 있다.
한 가지 헷갈리면 안 되는 부분을 정리하고 넘어가자면, 데이터프레임과 시리즈의 인덱스는 pandas
자료구조 이름으로서의 인덱스와 별개이다.
데이터프레임에서 말하는 인덱스(pandas.DataFrame.index
)는 데이터프레임에서 각 행이 갖는 레이블을 의미한다. 시리즈에서 말하는 인덱스(pandas.Series.index
)는 시리즈의 각 요소(항목)들이 갖는 레이블을 의미한다. 만약 데이터프레임의 열을 추출해서 시리즈로 만들었다면, 데이터프레임의 인덱스와 시리즈의 인덱스는 같을 것이다. 그러나 데이터프레임의 행을 추출해서 시리즈로 만들었다면, 데이터프레임의 행 레이블(pandas.DataFrame.columns
)이 시리즈의 인덱스가 될 것이다.
또한, pandas
에는 pandas.Index
라는 자료구조가 존재하는데, 이는 pandas
내의 객체들의 레이블(행이든 열이든)에 해당하는 자료구조이다. 데이터프레임의 행 레이블과 열 레이블은 모두 pandas.Index
객체이고, 시리즈의 인덱스 역시 pandas.Index
객체이다. 이 pandas.Index
는 불변(Immutable)1이며 순서가 있는(Sequence)2 자료형으로, 내부에 있는 값들은 모두 해시가능(Hashable)3해야 한다. 또한, 인덱스(pandas.Index
)는 이름을 가질 수 있다.
정리하자면 아래와 같다.
pandas.DataFrame.index
: 데이터프레임의 행 레이블을 의미한다.pandas.DataFrame.columns
: 데이터프레임의 열 레이블을 의미한다.pandas.Series.index
: 시리즈의 항목 레이블을 의미한다.pandas.Index
: 위 3가지 레이블과 그 외에 레이블이 필요한 pandas
객체들이 갖는 레이블이 속한 자료형이다.
자료형으로서의 인덱스(pandas.Index
)의 조건만 만족한다면, 어떤 값이든 데이터프레임의 행 레이블이나 열 레이블로 올 수 있다. 이를테면 데이터프레임의 행 레이블이 문자열로 구성되어도 상관 없다. 반대로 열 레이블이 정수로 구성되어도 상관 없다. 위 이미지는 행 레이블이 State
라는 이름을 가지고, 문자열로 구성된 예제이다.
데이터프레임은 어떻게 만들 수 있는가? pandas.DataFrame()
을 이용해서 만들 수 있다. 크게 리스트를 활용하는 방법과 딕셔너리를 이용하는 방법을 알아보자.
첫번째 방법은 2차원 리스트를 이용하는 방법이다. 데이터프레임 내부의 데이터를 그대로 2차원 리스트의 형태로 나타내면 된다. 예를 들어 아래 코드와 그 실행 결과를 보자.
import pandas as pd
df = pd.DataFrame([
[10, "남", "잼민이"],
[20, "남", "군인"],
[17, "여", "군필여고생"],
])
이처럼 리스트를 이용한 경우 행별로 데이터를 나타냄을 알 수 있다.
또다른 방법은 딕셔너리를 이용하는 방법이다. 2차원 리스트를 이용하는 경우 각 리스트는 행을 나타냈다. 반면, 딕셔너리를 이용하는 경우 방향이 반대가 되어 각 딕셔너리는 열이 된다.
import pandas as pd
df = pd.DataFrame({
"나이": {"김00": 10, "이00": 20, "박00": 17},
"성별": {"김00": "남", "이00": "남", "박00": "여"},
"직업": {"김00": "잼민이", "이00": "군인", "박00": "군필여고생"},
})
보이는 것처럼, 딕셔너리를 이용하는 경우 각 열을 행별로 정의해주는 것을 알 수 있다. 또한, 딕셔너리는 각각 키(Key)를 가지기 때문에, 레이블 역시 딕셔너리의 키에 맞게 지정된 것을 볼 수 있다.
한편, 위 두 방법을 섞어서 쓸 수도 있다. 딕셔너리들의 리스트나, 값이 리스트인 딕셔너리도 가능하다. 그 결과가 어떨지는 생각해보고 직접 해보도록 하자. 그 외에 시리즈나 numpy
의 배열을 사용해서 만드는 것 또한 가능하다. 여러가지 방법을 생각해보고, pandas
의 공식문서를 참조해보도록 하자.
한편, 리스트를 이용해 데이터프레임을 만들거나 해서 행 레이블이나 열 레이블이 단순한 정수 인덱스인 경우, index
와 columns
인수를 통해 값을 정해줄 수 있다. 아래를 참조하자.
import pandas as pd
df = pd.DataFrame([
[10, "남", "잼민이"],
[20, "남", "군인"],
[17, "여", "군필여고생"],
],
index=["김00", "이00", "박00"],
columns=["나이", "성별", "직업"]
)
위에서 우리는 데이터프레임을 직접 만드는 방법을 배워보았다. 하지만 일반적으로 데이터프레임을 만드는 경우보다는 파일로 가져오거나, 다시 파일로 내보내는 경우가 많을 것이다. 그러한 경우 pandas.read_파일종류()
와 pandas.DataFrame.to_파일종류()
함수를 사용하게 될 것이다.
pandas
에서 데이터프레임으로 변환 가능한 파일들은 대략적으로 아래와 같다(전체 명단은 여기서 확인한다.).
pickle
저장된 파일을 데이터프레임으로 불러오기 위해서는 pandas.read_파일종류()
를 사용한다. 예를 들어 pandas.read_csv()
는 CSV 파일을 읽어와서 데이터프레임으로 반환하는 함수이다. 인수로는 파이썬의 open()
함수와 유사하게 파일의 경로를 나타내는 문자열이 필요하다.
데이터프레임을 파일로 저장하기 위해서는 pandas.DataFrame.to_파일종류()
를 사용한다. 예를 들어 df.to_csv()
는 df
데이터프레임을 CSV 파일로 저장하는 함수이다. 인수로는 마찬가지로 파일의 경로를 나타내는 문자열이 필요하다.
데이터프레임에서 원하는 부분만 가져오고 싶은 경우가 있다. 특정 열이나 행을 하나 또는 여럿 추출하는 일이 그것이다. 데이터프레임을 인덱싱 또는 슬라이싱하는 방법을 알아보자. 실습을 위해 (조금 전에 만든 것과 같은) 아래 데이터프레임 df
를 가정하자.
우선, 인덱싱 연산자 []
를 사용해서 열을 인덱싱할 수 있다. 예를 들어 df["나이"]
는 위 데이터프레임에서 "나이"
열만 시리즈의 형태로 추출한다.
주의할 점은, 인덱싱 연산자 []
를 이용한 인덱싱은 항상 열만 추출한다는 점이다. df["김00"]
은 "김00"
이란 열 레이블이 없기 때문에 오류이다. 마찬가지로 df[0]
은 열 레이블 중 정수 0
을 찾아내기 때문에 이름이 0
인 열이 존재하지 않는 한 오류를 내뱉는다.
한편, 여러 열을 추출하고 싶을 때는 리스트를 이용하면 된다. df["나이", "직업"]
은 "나이"
와 "직업"
열만 가져온다. 이렇게 리스트를 이용해서 인덱싱을 한 경우, 결과는 항상 데이터프레임이다. 심지어 df[["나이"]]
처럼 하나의 열만 가져온 경우에도, 시리즈가 아니라 열이 하나인 데이터프레임이 된다.
슬라이싱은 항상 행을 가져온다. 이때, 행 레이블이 아니라 행 번호로 슬라이싱을 해야 한다는 점에 주의하자. df[0:2]
는 리스트의 슬라이싱과 비슷하게 맨 앞의 행(0
)과 그 다음 행(1
)으로 총 두 행을 데이터프레임의 형태로 가져온다.
슬라이싱의 결과는 항상 데이터프레임이다. df[0:1]
은 하나의 행만을 포함하는 데이터프레임이며, df[0:0]
은 빈 데이터프레임이다.
또한 특이한 방법으로, 마스킹(Masking)이란 방식으로 데이터프레임을 인덱싱할 수 있다. 데이터프레임에 부울로 구성되고, 길이가 데이터프레임의 행 개수와 같고, 순서가 있는(Sequence, 시퀀스) 자료형(이를테면 리스트나 시리즈)을 넘겨주면, True
인 인덱스의 행만 추출하게 된다.
예를 들어, df[[True, False, True]]
는 0번째, 2번째 행만 가져오게 된다.
나아가, 시리즈에 비교 연산을 하게 되면 부울 시리즈를 반환하는데, 이를 활용해서 특정 조건에 해당하는 행만 빠르게 가져올 수도 있다.
예를 들어, df[df["나이"] > 15]
는 "나이"
열이 15
보다 큰 행만 가져오게 된다.
한편, 여러 개의 조건을 이용해 마스킹을 할 때는, and
, or
, not
과 같은 논리 연산자가 아니라, 비트 연산자 &
, |
, ~
를 사용해야 한다.
예를 들어, df[(df["나이"] > 15) & (df["직업"] != "군필여고생")]
은 "나이"
열이 15
보다 크고 "직업"
열이 "군필여고생"
이 아닌 행만 가져오게 된다.
in
을 이용한 마스킹은 불가능하다. 예를 들어 df[df["직업"] in ["군인", "군필여고생"]]
은 불가능하다. 이 경우, df[df["직업"].isin(["군인", "군필여고생"])]
처럼 pandas.Series.isin()
함수를 사용해야 한다. 이 예제는 "직업"
열이 "군인"
이거나 "군필여고생"
인 행만 가져오게 된다.
pandas.DataFrame.query()
함수를 사용할 수도 있다. 이 함수는 마스킹할 조건을 문자열 형태로 작성하면 이를 파싱해서 쿼리해주는 함수이다. 아래의 조건에 따라 쿼리문을 작성해주어야 한다.
"df["직업"] == "군인"
이 아니라 직업 == "군인"
`
)을 사용해야 한다.
띄어쓰기 열이름 > 3
이 아니라 `띄어쓰기 열이름` > 3
and
, or
, not
과 같은 논리연산자나 in
을 사용한다.
나이 > 15 & 직업 != 군필여고생
이 아니라 나이 > 15 and 직업 != 군필여고생
@
를 변수명 앞에 붙인다.
나이 > my_age
가 아니라 나이 > @my_age
pandas.DataFrame.loc
은 행과 열 단위의 인덱싱을 쉽게 하기 위한 속성이다. loc
은 함수가 아니라 단순한 속성이며, df.loc[0]
과 같이 인덱싱 연산자 []
을 이용해 사용한다. 아래는 loc
을 사용하는 여러가지 방법이다.
df.loc["김00", "나이"]
는 "김00"
행의 "나이"
열에 접근한다. 즉 결과는 정수 10
이다. 이처럼 튜플을 이용해4 인덱싱하는 경우 앞쪽은 행, 뒤쪽은 열을 의미한다.
df.loc[["김00", "이00"], ["나이", "직업"]]
와 같이, 값 대신 리스트를 인덱싱에 사용하게 되면, 데이터프레임 인덱싱에서 그랬던 것처럼 해당 행이나 열을 모두 포함하게 된다.
슬라이싱을 이용할 수도 있는데, df.loc["김00":"이00", "나이":"성별"]
과 같이 사용한다. 이때 주의점은, loc
을 사용한 슬라이싱은 일반 슬라이싱과 다르게 Inclusive하게 작동한다는 점이다. 그말인즉슨 끝나는 인덱스 역시 슬라이싱 범위에 포함된다.
0:3
은 0
부터 2
까지이다.loc
을 사용하는 경우 0:3
은 0
부터 3
까지이다.
마스킹 역시 사용 가능하다. df.loc[[True, False, False], [True, True, False]]
와 같이 행과 열에 모두 부울 리스트를 넘겨주어 마스킹할 수 있다.
그리고 레이블 인덱싱, 리스트를 이용한 인덱싱, 슬라이싱, 마스킹을 원하는 대로 섞어서 사용할 수도 있다.
한편, iloc
은 인덱스를 이용한 loc
을 의미한다. 여기서 인덱스는 pandas
의 인덱스가 아니라, 파이썬 리스트나 튜플, 문자열 따위에서 볼 수 있는 0
부터 시작하는 정수 인덱스를 의미한다.
예를 들어, df.iloc[0:2, 0:2]
는 위에서부터 두 개의 행과 왼쪽에서부터 두 개의 열을 가져온다. loc
과 마찬가지로 여러가지 방법의 인덱싱이 모두 가능하다. 이러한 iloc
은 상황에 따라 loc
보다 더 간편할 수는 있지만, 몇 가지 단점을 가지고 있다.
따라서, 가능하면 loc
을 사용하는 것이 더 이해하기 쉬운 코드를 작성하는 데에 도움이 된다.
간단하게 데이터프레임에서 열을 추가하고 삭제하는 방법을 알아보자.
데이터프레임을 만들기 위해 딕셔너리를 사용한 것을 기억하는가? 열을 만드는 것은 딕셔너리에 새로운 키: 값 쌍을 추가하는 것과 비슷하다. 예를 들어 아래는 df
데이터프레임에 "인사법"
열을 추가하는 방법이다.
df["인사법"] = ["안녕하세요", "안녕", "안녕하살법"]
참고로, 행을 추가할 일은 열을 추가할 일보다 훨씬 적지만, 그 경우 pandas.DataFrame.append()
함수를 사용하면 된다.
한편, 열을 삭제하기 위해서는 어떻게 해야 할까? pandas.DataFrame.drop()
함수를 사용하면 행이나 열을 삭제할 수 있다. drop()
의 사용법은 아래와 같다.
pandas.DataFrame.drop(labels, axis)
: labels
로 단일 레이블 이름이나 레이블들의 리스트를 넘겨주고, axis
는 삭제할 대상이 행인지 열인지를 밝힌다. 0
이나 "index"
는 행을 삭제한다. 1
이나 "columns"
는 열을 삭제한다.
df.drop("성별", axis=1)
은 df
에서 "성별"
열을 삭제한 결과를 반환한다.pandas.DataFrame.drop(index, columns)
: index
로 삭제할 행 레이블이나 리스트를 넘겨주고, columns
로 삭제할 열 레이블이나 리스트를 넘겨준다.
df.drop(columns=["성별", "직업"])
은 "성별"
열과 "직업"
열을 삭제한 결과를 반환한다.Inplace란 제자리라는 뜻이다. 기본적으로 pandas
의 대부분 함수는 데이터프레임을 수정하면 수정한 데이터프레임을 새롭게 반환한다. 이러한 작동은 Inplace라고 볼 수 없다. 반면 Inplace인 경우 데이터프레임을 수정하면 수정한 결과가 원래 데이터프레임에 적용된다.
일반적인 파이썬의 예제로는 리스트와 문자열을 생각해보면 된다. 문자열에 대해 str.replace()
등으로 수정을 가하면 원래 문자열은 그대로 있고, 새로운 문자열이 반환된다. 이런 것은 Inplace가 아니다. 반면, list.append()
등으로 리스트를 수정하면 반환값은 없고 원래 리스트가 수정된다. 이런 것은 Inplace다.
pandas
역시 마찬가지다. pandas
의 수많은 함수는 inplace
인수를 갖는다. 기본값은 False
이며 이 경우 데이터프레임의 수정 결과는 새로운 데이터프레임으로 반환된다. inplace=True
라면 데이터프레임의 수정 결과는 원본에 바로 반영되고, 반환값은 None
이다. 아래 예제를 살펴보자.
# df가 이미 정의되어 있다고 가정
# 원본 데이터프레임은 그대로이고 새로운 df2 데이터프레임 생성
df2 = df.drop(columns="성별")
# 원본 데이터프레임을 변형하고 반환값은 없음
df.drop(columns="성별", inplace=True)
몇 가지 유용한 함수와 속성들을 알아보자. 일종의 치트시트로 활용하는 것도 좋다.
pandas.DataFrame.info()
는 데이터프레임의 정보를 요약해서 보여준다.pandas.DataFrame.describe()
는 각종 통계 계산 결과를 요약해서 보여준다.pandas.DataFrame.size
는 행(데이터 포인트)의 개수를 알려준다.pandas.DataFrame.shape
는 행과 열의 개수를 알려준다.pandas.DataFrame.index
는 행 레이블만 추출한다.pandas.DataFrame.columns
는 열 레이블만 추출한다.pandas.DataFrame.values
는 데이터 값들만 추출한다.pandas.DataFrame.head(n)
은 위에서부터 n
개의 데이터만 보여준다.pandas.DataFrame.sample(n)
은 n
개의 데이터를 무작위로 샘플링(표집)한다.pandas.Series.sort_values([ascending])
은 시리즈를 정렬하며, ascending
이 True
면 오름차순, False
면 내림차순으로 정렬한다.pandas.DataFrame.sort_values(by[, ascending])
은 데이터프레임을 by
열을 기준으로 정렬하며, ascending
이 True
면 오름차순, False
면 내림차순으로 정렬한다.pandas.Series.value_counts()
는 시리즈에서 각 값의 등장 횟수를 세어준다.pandas.DataFrame.value_counts()
는 데이터프레임에서 각 행의 값이 같은 경우의 개수를 세어준다.pandas.unique(array_like)
는 array_like
에서 고유한 값들만 배열 형태로 반환한다.Group By, 말 그대로 특정 열을 기준으로 그룹을 만든다는 뜻이다. SQL을 알고 있다면 집계함수를 이용하기 위해 그룹을 만드는 GROUP BY
를 들어본 적이 있을 것이다. pandas.DataFrame.groupby()
역시 이와 유사하다. 즉, 특정 열을 기준으로 그룹을 만들고, 이 그룹에 집계함수를 적용한다.
groupby()
의 단계는 크게 세 가지로 생각할 수 있다.
우선, groupby()
를 위해서는 어떤 열을 기준으로 그룹을 만들지에 대한 기준이 필요하다. 예를 들어 df.groupby("성별")
은 "성별"
열이 같은 행끼리 그룹으로 묶는다는 의미이다.
여러 개의 열을 기준으로 삼을 수도 있다. 이때는 리스트를 활용한다. df.groupby(["성별", "거주지"])
는 "성별"
과 "거주지"
열을 기준으로 두 열이 모두 동일한 행끼리 그룹으로 묶는다는 의미이다.
pandas.DataFrame.groupby()
의 결과는 pandas.api.typing.DataFrameGroupBy
라고 불리는 자료형이다. 이 자료형은 말 그대로 그룹별로 나누어진 데이터프레임이고, 각 그룹별로 여러가지 집계함수를 사용할 수 있다(구체적으로 어떤 메소드가 존재하는지는 여기를 확인하자). 예를 들어, df.groupby("성별").max()
는 데이터프레임 df
를 "성별"
열별로 그룹화하고, 그룹별로 "성별"
이 아닌 다른 열들에 대하여 max()
를 집계함수로 적용하여 최댓값을 구하게 된다. 따라서 결과는 데이터프레임인데, 각 행은 그룹이 되고, 열은 집계함수를 적용한 결과가 된다.
만약 커스텀 집계함수를 만들고 싶다면 agg()
함수를 사용할 수 있다. pandas.api.typing.DataFrameGroupBy.agg()
는 아래와 같이 작동한다.
grouped_df.agg(lambda x: sum(x) + 1)
은 각 그룹의 열별로 합에 1
을 더한 값을 반환한다.grouped_df.agg('max')
는 각 그룹의 열별로 최댓값을 반환한다.grouped_df.agg(['max', lambda x: sum(x) + 1])
인 경우 각 그룹의 열별로 최댓값 및 합에 1
을 더한 값을 반환한다.grouped_df.agg({'나이': 'max', '연소득': ['max', 'min']})
는 "나이"
열에 대해서는 최댓값을, "연소득"
열에 대해서는 최댓값과 최솟값을 반환한다.일반적으로 프로그래밍의 맥락에서 접근자(Accessor)는 프로그래밍에서 특정 객체의 속성에 접근하는 함수를 의미한다. 소위 말하는 Getter와 Setter를 접근자라고 할 수 있다.
여기서 다루고 싶은 접근자는 pandas.Series
의 특정 자료형 접근자(Dtype Specific Accessor)이다. 일반적으로 시리즈는 하나의 타입을 갖는다. 그렇지 않은 경우 시리즈의 타입(dtype
)은 임의의 파이썬 객체임을 나타내는 object
로 지정된다.
이때, 시리즈의 접근자는 특정 자료형을 가진 시리즈에 손쉽게 그 자료형에 대한 함수를 적용할 수 있게 해준다. 예를 들어, 문자열 시리즈에 대해 문자열 메소드 str.split()
을 사용하고 싶다고 하자. 아래와 같은 코드는 불가능하다.
import pandas as pd
ser = pd.Series(["hello world", "goodby python"])
ser.split() # Error!
위 코드가 오류를 발생시키는 이유는 split()
이 문자열에 정의된 메소드이지, 시리즈에 정의된 것이 아니기 때문이다. 즉 ser.split()
은 시리즈 ser
의 각 항목에 대해 split()
을 적용하라는 뜻이 아니라, ser
자체에 대해 split()
을 적용하라는 뜻이 된다. 하지만 시리즈에는 split()
메소드가 정의되어 있지 않기 때문에 오류가 발생한다.
이때, 시리즈의 각 항목에 대해 함수를 적용할 수 있도록 몇개의 접근자가 지정되어 있다. 예를 들어, 위 문제에서는 pandas.Series.str
접근자를 사용해서 ser.str.split()
이라는 함수를 사용하면 문제를 해결할 수 있다.
pandas
에는 아래와 같은 접근자가 지정되어 있다.
pandas.Series.str
: 문자열 접근자로, 각 항목이 문자열인 시리즈에 대해 사용할 수 있다.pandas.Series.cat
: 카테고리형 접근자로, 각 항목이 범주형인 시리즈에 대해 사용할 수 있다.pandas.Series.dt
: datetime
접근자로, 각 항목이 datetime.datetime
나 datetime.timedelta
와 같은 날짜형인 시리즈에 사용할 수 있다.pandas.Series.sparse
: 희소 데이터 접근자로, 각 항목이 거의 0
인 희소 데이터 시리즈에 대해 사용할 수 있다.pandas.Series.list
: 리스트 데이터 접근자로, 각 항목이 list
인 시리즈에 사용할 수 있다.pandas.Series.struct
: struct
데이터 접근자로, 각 항목이 dict
와 같은 형태인 시리즈에 사용할 수 있다.그렇다면 접근자가 없는 자료구조에 대해서 시리즈의 각 항목마다 함수를 적용하고 싶을 땐 어떻게 해야 할까? apply()
, map()
, replace()
함수를 사용할 수 있다. 이 세 함수는 모두 pandas.DataFrame
의 메소드로도, pandas.Series
의 메소드로도 존재한다. 하나씩 살펴보도록 할텐데, 요약하자면 아래와 같다.
pandas.Series.apply()
: 시리즈의 각 항목에 함수를 적용하고 싶다.pandas.Series.map()
: 시리즈의 각 항목에 함수를 적용하거나, 딕셔너리나 다른 시리즈로 매핑하고 싶다.pandas.Series.replace()
: 시리즈에서 특정한 항목만 다른 값으로 바꾸고 싶다.pandas.DataFrame.apply()
: 데이터프레임의 각 행별로 또는 열별로 함수를 적용하고 싶다.pandas.DataFrame.map()
: 데이터프레임의 개별 값 하나하나에 함수를 적용하고 싶다.pandas.DataFrame.replace()
: 데이터프레임에서 특정한 값만 다른 값으로 바꾸고 싶다.Apply는 우리말로 적용하다라는 뜻이다. 그 말대로 특정 함수를 데이터프레임이나 시리즈에 적용하는 데에 사용할 수 있다. pandas.Series.apply()
는 시리즈의 각 항목에 함수를 적용해서, 그 결과를 다시 시리즈로 반환한다(인덱스는 유지된다).
pandas.DataFrame.apply()
는 데이터프레임의 각 행 또는 열별로 함수를 적용해서, 그 결과를 시리즈 또는 데이터프레임으로 반환한다. 데이터프레임에 Apply를 사용하는 경우 가능한 방식의 수가 매우 많고, 그만큼 여러가지 방법으로 사용할 수 있다. 자세한 사용 방법은 공식문서를 참조하라.
Map은 파이썬 내장 map()
과 동일한 의미이다. 함수형 프로그래밍에서의 Map, Filter, Reduce에서의 그 Map을 의미한다. 특히, 여기서 Map과 Reduce는 빅데이터를 다루는 방식인 맵리듀스(MapReduce)와도 큰 연관을 갖는다.
(함수형 프로그래밍에 관한 것은 검색해보거나 추후 등장할 프로그래밍언어 게시글을 참조하라.)
(맵리듀스에 대한 아주 간단한 설명은 데이터과학 게시글 극후반대에 등장할 수도 있다.)
아무튼, Map이란 여러 데이터를 가진 자료구조에서 값을 하나씩 꺼내 특정 함수를 적용하고 그 결과를 다시 모아서 반환하는 패턴을 의미한다. pandas
의 map()
은 함수 대신 시리즈나 딕셔너리를 이용할 수도 있다.
시리즈의 경우, 함수를 이용하면 Map은 Apply처럼 시리즈의 각 항목에 해당 함수를 적용해서 다시 시리즈로 반환한다. 또한, 딕셔너리나 시리즈를 이용하는 경우 원래 시리즈의 각 항목을 딕셔너리의 키나 시리즈의 인덱스에서 찾아서 매핑하게 된다.
하지만 데이터프레임의 경우 Map과 Apply의 작동이 조금 다른데, pandas.DataFrame.map()
은 행이나 열 단위가 아니라 각 값 단위로 적용된다. 따라서 데이터프레임 상의 모든 값에 각각 특정 함수가 적용된 데이터프레임이 결과로 나오게 된다.
Replace는 대체하다라는 뜻이다. 즉, 시리즈나 데이터프레임에서 특정 값을 다른 값으로 대체하는 데에 초점이 맞춰져 있는 함수이다. Replace 역시 다양한 방법으로 사용이 가능한다. 시리즈에서의 작동과 데이터프레임에서의 작동이 대동소이하다.
대략적으로 살펴보자면, 다음과 같다. 우선 Replace에서 중요한 인수는 두 개가 있다. 첫째는 데이터프레임이나 시리즈 내부에서 선택할 값이고, 둘째는 그 값을 어떤 값으로 바꿀지이다.
먼저 대상을 선택해야 한다. 그 값은 정수, 실수, 문자열, 정규표현식, 리스트, 딕셔너리, 시리즈, None
일 수 있고, 각각의 경우 작동이 다르지만 정수나 실수, 문자열과 같은 원자값(Atomic Value)인 경우 해당 값 자체를 선택하고, 리스트인 경우 리스트에 포함된 모든 값을 선택한다.
그 후에 선택한 대상을 무엇으로 바꿀지 결정한다. 이 경우에도 원자값인 경우 선택된 모든 값이 그 값으로 바뀌고, 리스트인 경우 인덱스별로 매핑되게 된다(예를 들어 [0, 1]
과 [3, 4]
는 0
은 3
으로, 1
은 4
로 바꾸라는 의미이다).
오늘은 pandas
와 데이터프레임에 대해 매우 많은 것들을 다루었다. 이 글의 구성과 내용 절반 정도는 전술한 데이터과학 강의에서 따온 것이지만, 나머지는 기본적으로 알아야 할 내용들을 개인적으로 추가하고 보강한 것이다. 적어도 이 내용들을 알고 있다면 기초적인 부분은 전부 숙지하고 있다고 할 수 있겠다.
다음에는 이어서 데이터 클리닝, EDA, 정규표현식, 시각화 등을 순차적으로 다루어 나갈 예정이다.
문제 링크 문제 링크
개요 선형적인 자료구조에서는 값에 접근하는 데에 \(O(1)\)이면 충분하지만, 대신 부분합을 구하는 데에는 \(O(N)\)이 필요하다. 그렇다면 이 자료구조를 이진 트리로 구성하면 어떨까? 값에 접근하는 데에 걸리는 시간이 \(O(\lg N)\)으로 늘어나지만 대신 부분합을 구하...
개요 다익스트라 알고리즘과 함께 Single Sourse Shortest Path(SSSP) 문제를 푸는 알고리즘이다. 즉, 한 노드에서 다른 모든 노드로 가는 최단 경로를 구하는 알고리즘이다. 다익스트라 알고리즘보다 느리지만, 음수 가중치 간선이 있어도 작동하며, 음수 가중치 사...
개요 다익스트라 알고리즘은 Single Sourse Shortest Path(SSSP) 문제를 푸는 알고리즘 중 하나이다. 즉, 한 노드에서 다른 모든 노드로 가는 최단 경로를 구하는 알고리즘이다. 단, 다익스트라 알고리즘은 음수 가중치 엣지를 허용하지 않는다. 이 경우에는 벨만-...