(전공복습) 데이터과학 1. Pandas 사용법

차례

들어가기 전에

이 글은 컴퓨터학과 이중전공생으로서 배운 것들을 다시 한번 정리하고자 남기는 글입니다. 불완전한 기억 등의 이유로 오류가 있을 수 있으며, 참조 내지 이정표로만 사용해주세요.
본 게시글은 고려대학교의 데이터과학 강의를 기반으로 작성자의 추가적인 설명이 덧붙여진 글입니다.

데이터프레임(DataFrame)

데이터프레임의 구조
파이썬(pandas)이나 R을 다루다 보면 데이터프레임이라는 말을 들을 수 있다. 간략하게 말하자면, 데이터프레임은 표를 의미한다. 2차원 자료구조로서 행(Row, 가로줄)과 열(Column, 세로줄)을 가지고 있고, 일반적으로 각 열은 피처(Feature)에 해당하며, 행은 개별 데이터 포인트(Data Point)에 해당한다.
또한, 데이터프레임에는 행과 열에 대한 레이블이 존재한다. 이 레이블들은 각 행과 열을 대표하는 이름을 지닌다. 추가로, 각 열은 도메인(Domain, 정의역)을 가질 수 있는데, 이는 피처의 가능한 값에 대한 정의라고 할 수 있고, 더 간단하게는 그냥 그 열의 타입(Type, 자료형)이라고 생각할 수 있다.

데이터프레임 예제
위 이미지는 파이썬 데이터프레임의 예시이다. 우리는 위 데이터프레임을 보고 아래와 같은 사실들을 알 수 있다.

  • 데이터프레임은 연도별 미국 대선 후보의 투표 결과를 나타내고 있다.
  • 행의 레이블은 정수 인덱스의 형태로 나타나고 있다.
  • 열의 레이블은 각각 후보명, 정당, 퍼센티지, 년도, 결과이다.
  • 후보명은 대선 후보의 이름 문자열이다.
  • 정당은 가능한 정당의 명칭 문자열이다.
  • 퍼센티지는 0~100 사이의 실수이다.
  • 년도는 가능한 년도의 정수이다.
  • 결과는 win 또는 loss이다.

Pandas 자료구조

그렇다면 pandas에서 구현된 데이터프레임은 어떤 형태일까? 조금 전에 살펴본 예시가 바로 파이썬 pandas에서의 데이터프레임이다.

데이터프레임(Pandas DataFrame)

위에서 이야기한 것처럼, 행과 열로 구성된 2차원 자료구조로서, 열 레이블(columns)과 행 레이블(index), 값(values)으로 구성된다. 실질적인 데이터는 값들이고, 나머지는 열과 행을 위한 식별자라고 생각할 수 있다. 또한, 각 열은 dtype, 즉 타입을 가지며 이것이 도메인의 역할을 하게 된다.
(다만, 레이블들이 고유할 필요는 없다. 그러나 일반적으로는 이를 고유하게 구성하는 것이 권장된다. 특히, 열 레이블에서 더욱 그렇다.)

시리즈(Series)

또한 pandas에는 시리즈(Series)라는 자료구조가 존재한다. 열과 행 레이블을 가진 2차원 자료구조인 데이터프레임과 달리, 시리즈는 1차원 자료구조를 의미한다. 행을 추출하든, 열을 추출하든, 데이터프레임에서 한 줄을 추출해내면 그 결과가 시리즈이다. 다만, 하나의 열을 추출하는 것이 더 일반적이다.
시리즈는 데이터프레임의 행 레이블(인덱스)과 유사하게, 각 항목별로 인덱스를 가지고 있고, 그 시리즈의 이름(name)과 타입(dtype)을 갖는다.

인덱스(Index)

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의 공식문서를 참조해보도록 하자.

레이블 설정하기

한편, 리스트를 이용해 데이터프레임을 만들거나 해서 행 레이블이나 열 레이블이 단순한 정수 인덱스인 경우, indexcolumns 인수를 통해 값을 정해줄 수 있다. 아래를 참조하자.

import pandas as pd
df = pd.DataFrame([
    [10, "남", "잼민이"],
    [20, "남", "군인"],
    [17, "여", "군필여고생"],
    ], 
    index=["김00", "이00", "박00"],
    columns=["나이", "성별", "직업"]
)

데이터프레임과 파일

위에서 우리는 데이터프레임을 직접 만드는 방법을 배워보았다. 하지만 일반적으로 데이터프레임을 만드는 경우보다는 파일로 가져오거나, 다시 파일로 내보내는 경우가 많을 것이다. 그러한 경우 pandas.read_파일종류()pandas.DataFrame.to_파일종류() 함수를 사용하게 될 것이다.

가능한 파일 종류

pandas에서 데이터프레임으로 변환 가능한 파일들은 대략적으로 아래와 같다(전체 명단은 여기서 확인한다.).

  • CSV
  • 고정폭 텍스트 파일
  • JSON
  • HTML
  • LaTeX
  • XML
  • 클립보드
  • Excel이나 OpenDocument
  • SQL
  • 파이썬 pickle
  • Stata, SAS, SPSS와 같은 통계 프로그램 포맷
  • 그 외에 다양한 파일 형식

파일 불러오기

저장된 파일을 데이터프레임으로 불러오기 위해서는 pandas.read_파일종류()를 사용한다. 예를 들어 pandas.read_csv()는 CSV 파일을 읽어와서 데이터프레임으로 반환하는 함수이다. 인수로는 파이썬의 open() 함수와 유사하게 파일의 경로를 나타내는 문자열이 필요하다.

파일 저장하기

데이터프레임을 파일로 저장하기 위해서는 pandas.DataFrame.to_파일종류()를 사용한다. 예를 들어 df.to_csv()df 데이터프레임을 CSV 파일로 저장하는 함수이다. 인수로는 마찬가지로 파일의 경로를 나타내는 문자열이 필요하다.

데이터프레임 인덱싱(DataFrame Indexing)

데이터프레임에서 원하는 부분만 가져오고 싶은 경우가 있다. 특정 열이나 행을 하나 또는 여럿 추출하는 일이 그것이다. 데이터프레임을 인덱싱 또는 슬라이싱하는 방법을 알아보자. 실습을 위해 (조금 전에 만든 것과 같은) 아래 데이터프레임 df를 가정하자.
데이터프레임 인덱싱 예제

열 인덱싱 예제
우선, 인덱싱 연산자 []를 사용해서 열을 인덱싱할 수 있다. 예를 들어 df["나이"]는 위 데이터프레임에서 "나이" 열만 시리즈의 형태로 추출한다.
주의할 점은, 인덱싱 연산자 []를 이용한 인덱싱은 항상 열만 추출한다는 점이다. df["김00"]"김00"이란 열 레이블이 없기 때문에 오류이다. 마찬가지로 df[0]은 열 레이블 중 정수 0을 찾아내기 때문에 이름이 0인 열이 존재하지 않는 한 오류를 내뱉는다.

열 리스트 인덱싱 예제
한편, 여러 열을 추출하고 싶을 때는 리스트를 이용하면 된다. df["나이", "직업"]"나이""직업" 열만 가져온다. 이렇게 리스트를 이용해서 인덱싱을 한 경우, 결과는 항상 데이터프레임이다. 심지어 df[["나이"]]처럼 하나의 열만 가져온 경우에도, 시리즈가 아니라 열이 하나인 데이터프레임이 된다.

행 번호 슬라이싱 예제
슬라이싱은 항상 행을 가져온다. 이때, 행 레이블이 아니라 행 번호로 슬라이싱을 해야 한다는 점에 주의하자. df[0:2]는 리스트의 슬라이싱과 비슷하게 맨 앞의 행(0)과 그 다음 행(1)으로 총 두 행을 데이터프레임의 형태로 가져온다.
슬라이싱의 결과는 항상 데이터프레임이다. df[0:1]은 하나의 행만을 포함하는 데이터프레임이며, df[0:0]은 빈 데이터프레임이다.

마스킹(Masking)

마스킹 예제
또한 특이한 방법으로, 마스킹(Masking)이란 방식으로 데이터프레임을 인덱싱할 수 있다. 데이터프레임에 부울로 구성되고, 길이가 데이터프레임의 행 개수와 같고, 순서가 있는(Sequence, 시퀀스) 자료형(이를테면 리스트나 시리즈)을 넘겨주면, True인 인덱스의 행만 추출하게 된다.
예를 들어, df[[True, False, True]]는 0번째, 2번째 행만 가져오게 된다.

시리즈 마스킹 예제
나아가, 시리즈에 비교 연산을 하게 되면 부울 시리즈를 반환하는데, 이를 활용해서 특정 조건에 해당하는 행만 빠르게 가져올 수도 있다.
예를 들어, df[df["나이"] > 15]"나이" 열이 15보다 큰 행만 가져오게 된다.

논리 연산 시리즈 마스킹 예제
한편, 여러 개의 조건을 이용해 마스킹을 할 때는, and, or, not과 같은 논리 연산자가 아니라, 비트 연산자 &, |, ~를 사용해야 한다.
예를 들어, df[(df["나이"] > 15) & (df["직업"] != "군필여고생")]"나이" 열이 15보다 크고 "직업" 열이 "군필여고생"이 아닌 행만 가져오게 된다.

isin 마스킹 예제
in을 이용한 마스킹은 불가능하다. 예를 들어 df[df["직업"] in ["군인", "군필여고생"]]은 불가능하다. 이 경우, df[df["직업"].isin(["군인", "군필여고생"])]처럼 pandas.Series.isin() 함수를 사용해야 한다. 이 예제는 "직업" 열이 "군인"이거나 "군필여고생"인 행만 가져오게 된다.

쿼리(Query)

pandas.DataFrame.query() 함수를 사용할 수도 있다. 이 함수는 마스킹할 조건을 문자열 형태로 작성하면 이를 파싱해서 쿼리해주는 함수이다. 아래의 조건에 따라 쿼리문을 작성해주어야 한다.

  • 열 이름은 그냥 변수처럼 쓴다.
    • ex. "df["직업"] == "군인"이 아니라 직업 == "군인"
  • 열 이름에 띄어쓰기가 있다면 백틱(`)을 사용해야 한다.
    • ex. 띄어쓰기 열이름 > 3이 아니라 `띄어쓰기 열이름` > 3
  • 마스킹과 달리 and, or, not 과 같은 논리연산자나 in을 사용한다.
    • ex. 나이 > 15 & 직업 != 군필여고생이 아니라 나이 > 15 and 직업 != 군필여고생
  • 변수를 참조할 때는 @를 변수명 앞에 붙인다.
    • ex. 나이 > my_age가 아니라 나이 > @my_age

loc

pandas.DataFrame.loc은 행과 열 단위의 인덱싱을 쉽게 하기 위한 속성이다. loc은 함수가 아니라 단순한 속성이며, df.loc[0]과 같이 인덱싱 연산자 []을 이용해 사용한다. 아래는 loc을 사용하는 여러가지 방법이다.

df.loc["김00", "나이"]"김00" 행의 "나이" 열에 접근한다. 즉 결과는 정수 10이다. 이처럼 튜플을 이용해4 인덱싱하는 경우 앞쪽은 행, 뒤쪽은 열을 의미한다.

loc 예제 1
df.loc[["김00", "이00"], ["나이", "직업"]]와 같이, 값 대신 리스트를 인덱싱에 사용하게 되면, 데이터프레임 인덱싱에서 그랬던 것처럼 해당 행이나 열을 모두 포함하게 된다.

loc 예제 2
슬라이싱을 이용할 수도 있는데, df.loc["김00":"이00", "나이":"성별"]과 같이 사용한다. 이때 주의점은, loc을 사용한 슬라이싱은 일반 슬라이싱과 다르게 Inclusive하게 작동한다는 점이다. 그말인즉슨 끝나는 인덱스 역시 슬라이싱 범위에 포함된다.

  • 일반적인 슬라이싱에서 0:30부터 2까지이다.
  • loc을 사용하는 경우 0:30부터 3까지이다.

loc 예제 3
마스킹 역시 사용 가능하다. df.loc[[True, False, False], [True, True, False]]와 같이 행과 열에 모두 부울 리스트를 넘겨주어 마스킹할 수 있다.

그리고 레이블 인덱싱, 리스트를 이용한 인덱싱, 슬라이싱, 마스킹을 원하는 대로 섞어서 사용할 수도 있다.

iloc

한편, iloc은 인덱스를 이용한 loc을 의미한다. 여기서 인덱스는 pandas의 인덱스가 아니라, 파이썬 리스트나 튜플, 문자열 따위에서 볼 수 있는 0부터 시작하는 정수 인덱스를 의미한다.

iloc 예제
예를 들어, df.iloc[0:2, 0:2]는 위에서부터 두 개의 행과 왼쪽에서부터 두 개의 열을 가져온다. loc과 마찬가지로 여러가지 방법의 인덱싱이 모두 가능하다. 이러한 iloc은 상황에 따라 loc보다 더 간편할 수는 있지만, 몇 가지 단점을 가지고 있다.

  • 레이블이 아니라 번호를 기준으로 인덱싱하므로 혼동의 여지가 많다.
  • 마찬가지로, 실수로 잘못 인덱싱할 확률도 높다.
  • 같은 데이터에서 순서만 바뀌어도 결과가 달라진다.

따라서, 가능하면 loc을 사용하는 것이 더 이해하기 쉬운 코드를 작성하는 데에 도움이 된다.

열 다루기(Column Handling)

간단하게 데이터프레임에서 열을 추가하고 삭제하는 방법을 알아보자.

열 만들기

데이터프레임을 만들기 위해 딕셔너리를 사용한 것을 기억하는가? 열을 만드는 것은 딕셔너리에 새로운 키: 값 쌍을 추가하는 것과 비슷하다. 예를 들어 아래는 df 데이터프레임에 "인사법" 열을 추가하는 방법이다.

df["인사법"] = ["안녕하세요", "안녕", "안녕하살법"]

참고로, 행을 추가할 일은 열을 추가할 일보다 훨씬 적지만, 그 경우 pandas.DataFrame.append() 함수를 사용하면 된다.

열 삭제하기

한편, 열을 삭제하기 위해서는 어떻게 해야 할까? pandas.DataFrame.drop() 함수를 사용하면 행이나 열을 삭제할 수 있다. drop()의 사용법은 아래와 같다.

  1. pandas.DataFrame.drop(labels, axis): labels로 단일 레이블 이름이나 레이블들의 리스트를 넘겨주고, axis는 삭제할 대상이 행인지 열인지를 밝힌다. 0이나 "index"는 행을 삭제한다. 1이나 "columns"는 열을 삭제한다.
    • ex. df.drop("성별", axis=1)df에서 "성별" 열을 삭제한 결과를 반환한다.
  2. pandas.DataFrame.drop(index, columns): index로 삭제할 행 레이블이나 리스트를 넘겨주고, columns로 삭제할 열 레이블이나 리스트를 넘겨준다.
    • ex. df.drop(columns=["성별", "직업"])"성별" 열과 "직업" 열을 삭제한 결과를 반환한다.

Inplace

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 기능

몇 가지 유용한 함수와 속성들을 알아보자. 일종의 치트시트로 활용하는 것도 좋다.

기본 정보

  • 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])은 시리즈를 정렬하며, ascendingTrue면 오름차순, False면 내림차순으로 정렬한다.
  • pandas.DataFrame.sort_values(by[, ascending])은 데이터프레임을 by 열을 기준으로 정렬하며, ascendingTrue면 오름차순, False면 내림차순으로 정렬한다.
  • pandas.Series.value_counts()는 시리즈에서 각 값의 등장 횟수를 세어준다.
  • pandas.DataFrame.value_counts()는 데이터프레임에서 각 행의 값이 같은 경우의 개수를 세어준다.
  • pandas.unique(array_like)array_like에서 고유한 값들만 배열 형태로 반환한다.

groupby

Group By, 말 그대로 특정 열을 기준으로 그룹을 만든다는 뜻이다. SQL을 알고 있다면 집계함수를 이용하기 위해 그룹을 만드는 GROUP BY를 들어본 적이 있을 것이다. pandas.DataFrame.groupby() 역시 이와 유사하다. 즉, 특정 열을 기준으로 그룹을 만들고, 이 그룹에 집계함수를 적용한다.
groupby()의 단계는 크게 세 가지로 생각할 수 있다.

  1. 데이터프레임을 그룹별로 나눈다.
  2. 각 그룹에 집계함수를 적용한다.
  3. 2의 결과를 하나로 모은다.

우선, groupby()를 위해서는 어떤 열을 기준으로 그룹을 만들지에 대한 기준이 필요하다. 예를 들어 df.groupby("성별")"성별" 열이 같은 행끼리 그룹으로 묶는다는 의미이다.
여러 개의 열을 기준으로 삼을 수도 있다. 이때는 리스트를 활용한다. df.groupby(["성별", "거주지"])"성별""거주지" 열을 기준으로 두 열이 모두 동일한 행끼리 그룹으로 묶는다는 의미이다.

pandas.DataFrame.groupby()의 결과는 pandas.api.typing.DataFrameGroupBy라고 불리는 자료형이다. 이 자료형은 말 그대로 그룹별로 나누어진 데이터프레임이고, 각 그룹별로 여러가지 집계함수를 사용할 수 있다(구체적으로 어떤 메소드가 존재하는지는 여기를 확인하자). 예를 들어, df.groupby("성별").max()는 데이터프레임 df"성별" 열별로 그룹화하고, 그룹별로 "성별"이 아닌 다른 열들에 대하여 max()를 집계함수로 적용하여 최댓값을 구하게 된다. 따라서 결과는 데이터프레임인데, 각 행은 그룹이 되고, 열은 집계함수를 적용한 결과가 된다.

agg

만약 커스텀 집계함수를 만들고 싶다면 agg() 함수를 사용할 수 있다. pandas.api.typing.DataFrameGroupBy.agg()는 아래와 같이 작동한다.

  • 인수가 함수인 경우 해당 함수를 집계함수로 사용한다.
    • ex. grouped_df.agg(lambda x: sum(x) + 1)은 각 그룹의 열별로 합에 1을 더한 값을 반환한다.
  • 인수가 문자열인 경우 함수의 이름으로 해석된다.
    • ex. grouped_df.agg('max')는 각 그룹의 열별로 최댓값을 반환한다.
  • 인수가 함수 또는 함수 이름 문자열의 리스트인 경우 해당 함수들을 모두 적용한다.
    • ex. grouped_df.agg(['max', lambda x: sum(x) + 1])인 경우 각 그룹의 열별로 최댓값 및 합에 1을 더한 값을 반환한다.
  • 인수가 딕셔너리인 경우 열별로 다른 집계함수를 사용할 수도 있다.
    • ex. grouped_df.agg({'나이': 'max', '연소득': ['max', 'min']})"나이" 열에 대해서는 최댓값을, "연소득" 열에 대해서는 최댓값과 최솟값을 반환한다.

접근자(Accessor)

일반적으로 프로그래밍의 맥락에서 접근자(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.datetimedatetime.timedelta와 같은 날짜형인 시리즈에 사용할 수 있다.
  • pandas.Series.sparse: 희소 데이터 접근자로, 각 항목이 거의 0인 희소 데이터 시리즈에 대해 사용할 수 있다.
  • pandas.Series.list: 리스트 데이터 접근자로, 각 항목이 list인 시리즈에 사용할 수 있다.
  • pandas.Series.struct: struct 데이터 접근자로, 각 항목이 dict와 같은 형태인 시리즈에 사용할 수 있다.

Apply, Map, Replace

그렇다면 접근자가 없는 자료구조에 대해서 시리즈의 각 항목마다 함수를 적용하고 싶을 땐 어떻게 해야 할까? 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

Apply는 우리말로 적용하다라는 뜻이다. 그 말대로 특정 함수를 데이터프레임이나 시리즈에 적용하는 데에 사용할 수 있다. pandas.Series.apply()는 시리즈의 각 항목에 함수를 적용해서, 그 결과를 다시 시리즈로 반환한다(인덱스는 유지된다).
pandas.DataFrame.apply()는 데이터프레임의 각 행 또는 열별로 함수를 적용해서, 그 결과를 시리즈 또는 데이터프레임으로 반환한다. 데이터프레임에 Apply를 사용하는 경우 가능한 방식의 수가 매우 많고, 그만큼 여러가지 방법으로 사용할 수 있다. 자세한 사용 방법은 공식문서를 참조하라.

Map

Map은 파이썬 내장 map()과 동일한 의미이다. 함수형 프로그래밍에서의 Map, Filter, Reduce에서의 그 Map을 의미한다. 특히, 여기서 Map과 Reduce는 빅데이터를 다루는 방식인 맵리듀스(MapReduce)와도 큰 연관을 갖는다. (함수형 프로그래밍에 관한 것은 검색해보거나 추후 등장할 프로그래밍언어 게시글을 참조하라.)
(맵리듀스에 대한 아주 간단한 설명은 데이터과학 게시글 극후반대에 등장할 수도 있다.)

아무튼, Map이란 여러 데이터를 가진 자료구조에서 값을 하나씩 꺼내 특정 함수를 적용하고 그 결과를 다시 모아서 반환하는 패턴을 의미한다. pandasmap()은 함수 대신 시리즈나 딕셔너리를 이용할 수도 있다.

시리즈의 경우, 함수를 이용하면 Map은 Apply처럼 시리즈의 각 항목에 해당 함수를 적용해서 다시 시리즈로 반환한다. 또한, 딕셔너리나 시리즈를 이용하는 경우 원래 시리즈의 각 항목을 딕셔너리의 키나 시리즈의 인덱스에서 찾아서 매핑하게 된다.
하지만 데이터프레임의 경우 Map과 Apply의 작동이 조금 다른데, pandas.DataFrame.map()은 행이나 열 단위가 아니라 각 값 단위로 적용된다. 따라서 데이터프레임 상의 모든 값에 각각 특정 함수가 적용된 데이터프레임이 결과로 나오게 된다.

Replace

Replace는 대체하다라는 뜻이다. 즉, 시리즈나 데이터프레임에서 특정 값을 다른 값으로 대체하는 데에 초점이 맞춰져 있는 함수이다. Replace 역시 다양한 방법으로 사용이 가능한다. 시리즈에서의 작동과 데이터프레임에서의 작동이 대동소이하다.

대략적으로 살펴보자면, 다음과 같다. 우선 Replace에서 중요한 인수는 두 개가 있다. 첫째는 데이터프레임이나 시리즈 내부에서 선택할 값이고, 둘째는 그 값을 어떤 값으로 바꿀지이다.
먼저 대상을 선택해야 한다. 그 값은 정수, 실수, 문자열, 정규표현식, 리스트, 딕셔너리, 시리즈, None일 수 있고, 각각의 경우 작동이 다르지만 정수나 실수, 문자열과 같은 원자값(Atomic Value)인 경우 해당 값 자체를 선택하고, 리스트인 경우 리스트에 포함된 모든 값을 선택한다.
그 후에 선택한 대상을 무엇으로 바꿀지 결정한다. 이 경우에도 원자값인 경우 선택된 모든 값이 그 값으로 바뀌고, 리스트인 경우 인덱스별로 매핑되게 된다(예를 들어 [0, 1][3, 4]03으로, 14로 바꾸라는 의미이다).

정리

오늘은 pandas와 데이터프레임에 대해 매우 많은 것들을 다루었다. 이 글의 구성과 내용 절반 정도는 전술한 데이터과학 강의에서 따온 것이지만, 나머지는 기본적으로 알아야 할 내용들을 개인적으로 추가하고 보강한 것이다. 적어도 이 내용들을 알고 있다면 기초적인 부분은 전부 숙지하고 있다고 할 수 있겠다.

다음에는 이어서 데이터 클리닝, EDA, 정규표현식, 시각화 등을 순차적으로 다루어 나갈 예정이다.

  1. 불변(Immutable) 자료형이란 값을 수정, 추가, 삭제할 수 없는 자료형을 의미한다. 파이썬에서는 튜플이나 문자열 등이 대표적인 불변 자료형이다. 

  2. 순서가 있는(Sequence, 시퀀스) 자료형이란 말 그대로 파이썬의 문자열이나 리스트처럼 순서가 있는 자료형을 의미한다. 

  3. 해시가능(Hashable) 자료형이란 말 그대로 해시함수에 넘겨줄 수 있는 자료형을 의미한다. 파이썬에서 대부분의 경우, 불변 자료형은 해시가능하다. 

  4. 파이썬에서 쉼표(,)만으로 구분된 값의 자료형은 튜플이다. 

2024

맨 위로 이동 ↑

2023

세그먼트 트리

개요 선형적인 자료구조에서는 값에 접근하는 데에 \(O(1)\)이면 충분하지만, 대신 부분합을 구하는 데에는 \(O(N)\)이 필요하다. 그렇다면 이 자료구조를 이진 트리로 구성하면 어떨까? 값에 접근하는 데에 걸리는 시간이 \(O(\lg N)\)으로 늘어나지만 대신 부분합을 구하...

벨만-포드 알고리즘

개요 다익스트라 알고리즘과 함께 Single Sourse Shortest Path(SSSP) 문제를 푸는 알고리즘이다. 즉, 한 노드에서 다른 모든 노드로 가는 최단 경로를 구하는 알고리즘이다. 다익스트라 알고리즘보다 느리지만, 음수 가중치 간선이 있어도 작동하며, 음수 가중치 사...

다익스트라 알고리즘

개요 다익스트라 알고리즘은 Single Sourse Shortest Path(SSSP) 문제를 푸는 알고리즘 중 하나이다. 즉, 한 노드에서 다른 모든 노드로 가는 최단 경로를 구하는 알고리즘이다. 단, 다익스트라 알고리즘은 음수 가중치 엣지를 허용하지 않는다. 이 경우에는 벨만-...

맨 위로 이동 ↑