35 분 소요

Pandas

월스트리트 금융회사의 분석 전문가인 웨스 매키니(Wes McKinney)는 회사에서 사용하는 분석용 데이터 핸들링 툴이 마음에 안 들어서 판다스를 개발했다고 한다. 그가 생각하는 대부분의 데이터 분석 애플리케이션에서의 중요한 기능은 다음과 같다.

  • 벡터 배열 상에서 데이터 가공(데이터 먼징 또는 데이터 랭글링)1, 정제, 부분집합, 필터링, 변형 그리고 다른 여러 종류의 연산을 빠르게 수행

  • 정렬, 유일 원소 찾기, 집합 연산 같은 일반적인 배열 처리 알고리즘

  • 통계의 효과적인 표현과 데이터를 수집 요약하기

  • 다양한 종류의 데이터를 병합하고 엮기 위한 데이터 정렬과 데이터 간의 관계 조작

  • 내부에서 if-elif-else를 사용하는 반복문 대신 사용할 수 있는 조건절 표현을 허용하는 배열 처리

  • 데이터 묶음 전체에 적용할 수 있는 수집, 변형, 함수 적용 같은 데이터 처리

판다스는 파이썬에서 데이터 처리를 위해 존재하는 가장 인기 있는 라이브러리다. 일반적으로 대부분의 데이터 세트는 2차원 데이터이다. 즉, 행(Row) x 열(Column)로 구성돼 있다(RDBMS의 TABLE이나 엑셀의 시트를 떠올리면 된다). 행과 열의 2차원 데이터가 인기 있는 이유는 바로 인간이 가장 이해하기 쉬운 데이터 구조이면서도 효과적으로 데이터를 담을 수 있는 구조이기 때문이다. 판다스는 이처럼 행과 열로 이뤄진 2차원 데이터를 효과적으로 가공/처리할 수 있는 다양하고 훌륭한 기능을 제공한다.

판다스는 고수준의 자료구조와 피어썬에서 빠르고 쉽게 사용할 수 있는 데이터 분석 도구를 포함하고 있다. 판다스는 다른 산술 계산 도구인 NumPy와 SciPy, 분석 라이브러리인 statsmodels와 scikit-learn, 시각화 도구인 matplotlib과 함께 사용하는 경우가 흔하다.

판다스는 많은 부분이 넘파이 기반으로 작성됐지만 가장 큰 차이점은 판다스는 파이썬의 리스트, 컬렉션, 넘파이 등의 내부 데이터 뿐만 아니라 CSV 등의 파일을 쉽게 DataFrame으로 변경해 데이터의 가공/분석을 편리하게 수행할 수 있게 초점을 맞춰 설계했다는 것이다.

이에 판다스 라이브러리는 데이터를 수집하고 정리하는 데 최적화된 도구라고 볼 수 있다. 오픈소스(open source)라서 무료라는 장점도 있다. 또한 가장 배우기 쉬운 파이썬을 기반으로 하기 때문에 컴퓨터과학이나 프로그래밍을 전공하지 않은 사람들도 쉽게 따라가며 배우는 것이 가능하다. 판다스를 배우면 데이터과학의 80~90% 업무를 처리할 수 있고, 데이터과학자에게 필요한 기본적이면서도 아주 중요한 도구를 갖추게 된다.

판다스 라이브러리는 여러 종류의 클래스(class)와 다양한 내장 함수(built-in function)로 구성된다. 시리즈(Series)와 데이터프레임(DataFrame)은 데이터 구조를 표현하는 대표적인 클래스 객체다. 시리즈와 데이터프레임 클래스의 속성과 메소드를 잘 이해하면, 판다슬 자유자재로 다루는 데 어려움이 없을 것이다. 내장 함수로는 Series(), DataFrame(), read_csv(), read_excel() 등이 있다.

Pandas data structure

판다스는 서로 다른 형식을 갖는 여러 종류의 데이터를 컴퓨터가 이해할 수 있도록 동일한 형식을 갖는 구조로 통합하기 위해 시리즈(Series)와 데이터프레임(DataFrame)이라는 구조화된 데이터 형식을 제공한다. 이는 파이썬 클래스로 만들어지는데, 서로 다른 종류의 데이터를 한곳에 담는 그릇(컨테이너)이 된다.

판다스의 핵심 객체는 DataFrame으로 여러 개의 행과 열로 이뤄진 2차원 데이터를 담는 데이터 구조체이다. 판다스가 다루는 배부분의 영역은 DataFrame에 관련된 부분이다. DataFrame을 이해하기 전에 다른 중요 객체인 Index와 Series를 이해하는 것도 중요하다. Index는 RDMBS의 PK처럼 개별 데이터를 고유하게 식별하는 Key 값이다. Series와 DataFrame은 모두 Index를 key 값으로 가지고 있다. 이들의 가장 큰 차이는 Series는 칼럼이 하나 뿐인 데이터 구조체이고, DataFrame은 칼럼이 여러 개인 데이터 구조체라는 점이다.

컴퓨터에 설치된 판다스를 파이썬 파일(.py)에서 사용하려면, 판다스 라이브러리를 실행 환경으로 불러오는 박업이 필요하다. 이때 임포트(import) 명령을 사용한다. as로 패키지 약어를 지정해두면 함수를 사용할 때 패키지 이름 대신 약어를 활용해 코드를 짧게 줄일 수 있어 편리하다. 보통 패키지 개발자가 권하는 이름대로 다음과 같이 pandas의 import 관례convention를 사용한다.

import pandas as pd

Series

Series는 데이터가 순차적으로 나열된 1차원 배열의 형태를 갖는 자료구조다(어떤 NumPy 자료형이라도 담을 수 있다). 그리고 인덱스(index)라고 하는 데이터의 위치를 나타내는 이름(데이터 주소)을 갖고 있으며 데이터 값(value)과 일대일 대응이 된다. 이런 관점에서 키(k)와 값(v)이 ‘{k:v}’ 형태로 짝을 이루는 파이썬 딕셔너리(dictionary)와 비슷한 구조를 갖는다고 볼 수 있다.

Series 만들기

딕셔너리와 시리즈의 구조가 비슷하기 때문에 딕셔너리를 시리즈로 변환하는 방법을 많이 사용한다. 또는 배열 데이터로부터 생성할 수도 있다. 판다스 내장 함수인 Series()를 이용하고, 배열이나 딕셔너리를 함수의 인자로 전달한다. 딕셔너리를 인자로 넘겨줄 경우 딕셔너리의 키(k)는 시리즈의 인덱스에 대응하고, 딕셔너리의 각 키에 매치되는 값(v)이 시리즈의 데이터 값으로 변환된다.

# key:value 쌍으로 딕셔너리를 만들고, 변수 dict_data에 저장
dict_data = {'a': 1, 'b': 2, 'c': 3}

# 판다스 Series() 함수로 dictionary를 Series로 변환. 변수 sr에 저장
sr = pd.Series(dict_data)

# sr의 자료형과 객체 데이터 출력
print('sr의 자료형:', type(sr)) # type() 함수로 변수 sr에 저장된 객체의 자료형 확인
print(sr)
sr의 자료형: <class 'pandas.core.series.Series'>
a    1
b    2
c    3
dtype: int64

출력된 시리즈 객체 왼쪽의 ‘a’, ‘b’, ‘c’는 인덱스이고, 오른쪽의 ‘1’, ‘2’, ‘3’은 값으로 서로의 값에 대응된다. 마지막 줄의 출력을 보면 시리즈를 구성하는 데이터 값의 자료형(dtype)은 정수형(int64)이다.

인덱스 구조

판다스의 Index 객체는 RDMBS의 PK(Primary Key)와 유사하게 DataFrame, Series의 레코드를 유일하게 식별하는 객체로 자기와 짝을 이루는 데이터 값의 순서와 주소를 저장한다. 인덱스를 잘 활용하면 데이터 값의 탐색, 정렬, 선택, 결합 등 데이터 조작을 쉽게 할 수 있다.

인덱스는 크게 정수형 위치 인덱스(integer position)와 인덱스 이름(index name) 또는 인덱스 라벨(index label)의 두 가지 종류이다. 아래의 예시에서는 정수형 위치 인덱스를 보여준다.

# 리스트를 시리즈로 변환하여 변수 sr에 저장
list_data = ['2022-08-02', 3.14, 'ABC', 100, True]
sr = pd.Series(list_data)
print(sr)
0    2022-08-02
1          3.14
2           ABC
3           100
4          True
dtype: object

시리즈의 데이터 값 배열과 인덱스 객체는 각각 시리즈 클래스의 valuesindex 속성을 통해 얻을 수 있다.

# 인덱스 배열은 변수 idx에 저장. 데이터 값 배열은 변수 val에 저장.
idx = sr.index # range(5)와 동일.
val = sr.values

print('index:', idx)
print('values:', val)
index: RangeIndex(start=0, stop=5, step=1)
values: ['2022-08-02' 3.14 'ABC' 100 True]

인덱스는 0 ~ 4 범위의 정수를 갖는 RangeIndex 객체로 표시된다. 즉, 데이터의 인덱스를 지정하지 않았으므로 기본 인덱스인 정수 0에서 N - 1(N은 데이터의 길이)까지의 숫자가 표시된다. 데이터의 값 배열은 원래 데이터인 list_data의 리스트 원소 배열이 순서를 유지한 상태로 입력된다.

리스트 또는 튜플을 시리즈로 만들 때 정수형 위치 인덱스 대신 인덱스 이름을 지정할 수 있다. 각각의 데이터를 지칭하는 인덱스를 지정하여 Series 객체를 생성해야 할 때는 다음처럼 Series() 함수의 index 옵션에 인덱스 이름을 직접 전달한다. 또는, 딕셔너리 객체로 Series 객체를 생성할 때 인덱스의 순서를 직접 지정하고 싶다면 원하는 순서대로 인덱스를 직접 넘겨줄 때 사용한다.

sr = pd.Series(list_data, index=['d', 'b', 'a', 'c', 'e'])

print(sr)
d    2022-08-02
b          3.14
a           ABC
c           100
e          True
dtype: object
# 인덱스 배열은 변수 idx에 저장. 데이터 값 배열은 변수 val에 저장.
idx = sr.index
val = sr.values

print('index:', idx)
print('values:', val)
index: Index(['d', 'b', 'a', 'c', 'e'], dtype='object')
values: ['2022-08-02' 3.14 'ABC' 100 True]

원소 선택

원소의 위치를 나타내는 주소 역할을 하는 인덱스를 이용하여 시리즈의 원소를 선택한다. 하나의 원소를 선택할 수도 있고, 여러 원소를 한꺼번에 선택할 수도 있다. 파이썬 리스트 슬라이싱(slicing) 기법과 비슷하게 인덱스 범위를 지정하여 원소를 선택하는 방법도 있다.

정수형 위치 인덱스는 대괄호([]) 안에 위치를 나타내는 숫자를 입력하는데 반해, 인덱스 이름(라벨)을 사용할 때는 대괄호([]) 안에 이름과 함께 따옴표를 입력한다.

# 튜플을 시리즈로 변환(index 옵션 지정)
tup_data = ('영호', '2012-08-03', '남', True)
sr = pd.Series(tup_data, index=['이름', '생년월일', '성별', '학생여부'])
print(sr)
이름              영호
생년월일    2012-08-03
성별               남
학생여부          True
dtype: object
# 원소를 1개 선택
print(sr[0]) # sr의 1번째 원소를 선택(정수형 위치 인덱스)
print(sr['이름']) # '이름' 라벨을 가진 원소를 선택(인덱스 이름)
영호
영호
# 여러 개의 원소를 선택(인덱스 리스트 활용)
print(sr[[1, 2]])
print('\n')
print(sr[['생년월일', '성별']])
생년월일    2012-08-03
성별               남
dtype: object


생년월일    2012-08-03
성별               남
dtype: object
# 여러 개의 원소를 선택(인덱스 범위 지정)
print(sr[1:2])
print('\n')
print(sr['생년월일':'성별'])
생년월일    2012-08-03
dtype: object


생년월일    2012-08-03
성별               남
dtype: object

정수형 위치 인덱스를 사용할 때는 범위의 끝(2: 성별)이 포함되지 않았지만 인덱스 이름을 사용한 슬라이싱에서는 범위의 끝(2: 성별)이 포함된다.

Series 객체와 Series의 인덱스는 모두 name 속성이 있는데 이 속성은 pandas의 핵심 기능과 밀접한 관련이 있다.

sr.name = 'student' # Series 객체의 name
 
sr.index.name = 'info' # Series.index의 name

print(sr)
info
이름              영호
생년월일    2012-08-03
성별               남
학생여부          True
Name: student, dtype: object

DataFrame

데이터프레임은 2차원 배열이다. 행과 열로 만들어지는 2차원 배열 구조는 마이크로소프트 엑셀(Excel)과 관계형 데이터베이스(RDBMS) 등 컴퓨터 관련 다양한 분야에서 사용된다. 판다스의 데이터프레임 자료구조는 대표적인 통계 패키지인 R의 데이터프레임에서 유래했다고 알려져 있다.

데이터프레임은 여러 개의 칼럼이 있는데 각 칼럼은 서로 다른 종류의 값(숫자, 문자열, 불리언 등)을 담을 수 있다. 데이터프레임은 행과 열을 타나개이 위해 두 가지 종류의 주소를 사용한다. 각각 행 인덱스(row index)와 열 이름(column name 또는 column label)으로 구분한다.

데이터프레임의 열은 공통의 속성을 갖는 일련의 데이터를 나타내고, 행은 개별 관측대상에 대한 다양한 속성 데이터들의 모음인 레코드(record)가 된다. 즉, 각 행은 하나의 관측값(observation)을 나타내고, 열은 공통의 속성이나 범주를 나타내는데, 보통 변수(variable)로 활용된다.

row\column column0 column1 column2
row0 index(0, 0) index(0, 1) index(0, 2)
row1 index(1, 0) index(1, 1) index(1, 2)
row2 index(2, 0) index(2, 1) index(2, 2)

다음은 DataFrame 생성을 위한 입력 데이터의 종류다.

설명
2차원 ndarray 데이터를 담고 있는 행렬. 선택적으로 행(로우)와 열(칼럼)의 이름을 전달할 수 있다.
배열, 리스트, 튜플의 사전 사전의 모든 항목은 같은 길이를 가져야 하며, 각 항목의 내용이 DataFrame의 칼럼이 된다.
NumPy의 구조화 배열 배열의 사전과 같은 방식으로 취급된다.
Series의 사전 Series의 각 값이 칼럼이 된다. 명시적으로 인덱스를 넘겨주지 않으면 각 Series의 이니덱스가 하나로 합쳐져서 로우의 인덱스가 된다.
사전의 사전 내부에 있는 사전이 칼럼이 된다. 키 값은 ‘Series의 사전’과 마찬가지로 합쳐져서 로우의 인덱스가 된다.
사전이나 Series의 리스트 리스트의 각 항목이 DataFrame의 로우가 된다. 합쳐진 사전의 키 값이나 Series의 인덱스가 DataFrame의 칼럼 이름이 된다.
리스트나 튜플의 리스트 ‘2차원 ndarray’의 경우와 같은 방식으로 취급된다.
다른 DataFrame 인덱스를 따로 지정하지 않으면 DataFrame의 인덱스가 그대로 사용된다.
NumPy MaskedArray ‘2차원 ndarray’의 경우와 같은 방식으로 취급되지만 마스크값은 반환되는 DataFrame에서 NA 값이 된다.

데이터프레임 만들기

데이터프레임을 만들기 위해서는 같은 길이(원소의 개수가 동일한)의 1차원 배열 여러 개가 필요하다. 데이터프레임은 인덱스의 모양이 같은 여러 개의 시리즈(열, column) 객체를 담고 있는 파이썬 사전으로 생각하면 편하다. 데이터프레임을 만들 때는 판다스 DataFrame() 함수를 사용한다. 주로 여러 개의 리스트를 원소로 갖는 딕셔너리를 함수의 인자로 전달하는 방식이 활용된다. 이때 딕셔너리의 키(k)가 열 이름이 되고, 값(v)에 해당하는 각 리스트가 데이터프레임의 열이 된다. 행 인덱스에는 정수형 위치 인덱스가 자동 지정된다.

# 열이름을 key로 하고, 리스트를 value로 갖는 딕셔너리 정의(2차원 배열)
dict_data = {'c0': [1, 2, 3], 'c1': [4, 5, 6], 'c2': [7, 8, 9], 'c3': [10, 11, 12], 'c4': [13, 14, 15]}

# 판다스 DataFrame() 함수로 딕셔너리를 데이터프레임으로 변환. 변수 df에 저장
df = pd.DataFrame(dict_data)

# df의 자료형과 데이터프레임 객체 출력
print(type(df))
print(df)
<class 'pandas.core.frame.DataFrame'>
   c0  c1  c2  c3  c4
0   1   4   7  10  13
1   2   5   8  11  14
2   3   6   9  12  15

행 인덱스/열 이름 설정

데이터프레임의 구조적 특성 때문에 2차원 배열 형태의 데이터(중첩 리스트 또는 중첩 튜플)을 데이터프레임으로 변환하기 쉽다.

2차원 배열을 DataFrame() 함수 인자로 전달하여 데이터프레임으로 변환할 때 행 인덱스와 열 이름 속성을 사용자가 직접 지정할 수도 있다.

# 행 인덱스/열 이름 지정하여 데이터프레임 만들기
df = pd.DataFrame([[22, '남', '가우스대'], [20, '여', '르쿤대']],
                  index=['희준', '지은'],
                  columns=['나이', '성별', '학교'])

# 행 인덱스, 열 이름 확인하기
print(df) # 데이터프레임
print('index:', df.index) # 행 인덱스
print('columns:', df.columns) # 열 이름
    나이 성별    학교
희준  22  남  가우스대
지은  20  여   르쿤대
index: Index(['희준', '지은'], dtype='object')
columns: Index(['나이', '성별', '학교'], dtype='object')

2.2.1에서 예제에서는 딕셔너리의 원소인 리스트가 열이 됐지만, 이번 예제에서는 하나의 리스트가 행으로 변환되는 점에 유의한다.

데이터프레임 df의 행 인덱스와 열 이름 객체를 나타내는 df.indexdf.column의 속성에 새로운 배열을 할당하는 방식으로 행 인덱스와 열 이름을 변경할 수 있다.

df.index = ['학생1', '학생2']
df.columns = ['연령', '남녀', '소속']
print(df)
print('index:', df.index)
print('columns:', df.columns)
     연령 남녀    소속
학생1  22  남  가우스대
학생2  20  여   르쿤대
index: Index(['학생1', '학생2'], dtype='object')
columns: Index(['연령', '남녀', '소속'], dtype='object')

데이터프레임에 rename() 메서드를 적용하면 행 인덱스 또는 열 이름의 일부를 선택하여 변경할 수 있다. 단, 원본 객체를 직접 수정하는 것이 아니라 새로운 데이터프레임 객체를 반환한다. 원본 객체를 직접 변경하려면 inplace=True 옵션을 사용한다.

print('변경 전')
print(df) # 현재 df의 행 인덱스와 열 이름 확인

# 열 이름 바꾸기
df.rename(columns={'연령': '나이', '남녀': '성별', '소속': '학교'}, inplace=True)

# df의 행 인덱스 바꾸기
df.rename(index={'학생1': '희준', '학생2': '지은'}, inplace=True)

# df 출력(변경 후)
print('\n변경 후')
print(df)
변경 전
     연령 남녀    소속
학생1  22  남  가우스대
학생2  20  여   르쿤대

변경 후
    나이 성별    학교
희준  22  남  가우스대
지은  20  여   르쿤대

행/열 삭제

데이터프레임의 행 또는 열을 삭제하는 명령으로 drop() 메서드가 있다. 행을 삭제할 때는 축(axis) 옵션으로 axis=0(default)을 입력하고 열을 삭제할 때는 axis=1을 입력하여 삭제한다. 동시에 여러 개의 행 또는 열을 삭제하려면 리스트 형태로 입력한다.

한편 drop() 메서드는 기존 객체를 변경하지 않고 새로운 객체를 반환하므로 원본 객체를 직접 변경하기 위해서는 inplace=True 옵션을 추가한다. inplace 옵션을 False로 설정하면 새로운 객체가 생성되고, 원 객체는 변경 없이 그대로 유지된다.

# DataFrame() 함수로 데이터프레임 변환. 변수 df에 저장
exam_data = {'수학': [90, 80, 70], '영어': [98, 89, 95],
             '음악': [85, 95, 100], '체육': [100, 90, 90]}

df = pd.DataFrame(exam_data, index=['서준', '우현', '인아'])
print(df)
print('\n')

# 데이터프레임 df를 복제하여 변수 df2에 저장. df2의 1개 행(row) 삭제
df2 = df.copy()
df2.drop('우현', inplace=True)
print(df2)
print('\n')

# 데이터프레임 df를 복제하여 변수 df3에 저장. df3의 2개 행(row) 삭제
df3 = df.copy()
df3.drop(['우현', '인아'], axis=0, inplace=True)
print(df3)
    수학  영어   음악   체육
서준  90  98   85  100
우현  80  89   95   90
인아  70  95  100   90


    수학  영어   음악   체육
서준  90  98   85  100
인아  70  95  100   90


    수학  영어  음악   체육
서준  90  98  85  100
print(df)
print('\n')

# 데이터프레임 df를 복제하여 변수 df4에 저장. df4의 1개 열(column) 삭제
df4 = df.copy()
df4.drop('수학', axis=1, inplace=True)
print(df4)
print('\n')

# 데이터프레임 df를 복제하여 변수 df5에 저장. df5의 2개 열(column) 삭제
df5 = df.copy()
df5.drop(['영어', '음악'], axis=1, inplace=True)
print(df5)
    수학  영어   음악   체육
서준  90  98   85  100
우현  80  89   95   90
인아  70  95  100   90


    영어   음악   체육
서준  98   85  100
우현  89   95   90
인아  95  100   90


    수학   체육
서준  90  100
우현  80   90
인아  70   90

참고로 del 예약어를 이용해서 칼럼을 삭제할 수도 있다.

print(df5)
print('\n')

del df5['체육']

print(df5)
    수학   체육
서준  90  100
우현  80   90
인아  70   90


    수학
서준  90
우현  80
인아  70

drop() 메서드 정리

  • axis: DataFrame의 로우를 삭제할 때는 axis=0, 칼럼을 삭제할 때는 axis=1로 설정.

  • 원본 DataFrame은 유지하고 드롭된 DataFrame을 새롭게 객체 변수로 받고 싶다면 inplace=False로 설정(디폴트 값이 False).

  • 원본 DataFrame에 드롭된 결과를 적용할 경우에는 inplace=True를 적용.

  • 원본 DataFrame에서 드롭된 DataFrame을 다시 원본 DataFrame 객체 변수로 할당하면 원본 DataFrame에서 드롭된 결과를 적용할 경우와 같음(단, 기존 원본 DataFrame 객체 변수는 메모리에서 추후 제거됨).

행 선택

데이터프레임의 행 데이터를 선택하기 위해서는 lociloc 인덱서를 사용한다. 인덱스 이름을 기준으로 행을 선택할 때는 loc을 이용하고, 정수형 위치 인덱스를 사용할 때는 iloc을 이용한다.

iloc[]는 위치 기반 인덱싱만 허용하기 때문에 행과 열 값으로 integer 또는 integer형의 슬라이싱, 팬시 리스트 값을 입력해줘야 한다(불링 인덱싱은 조건을 기술하므로 이에 제약받지 않는다). iloc[]는 슬라이싱과 팬시 인덱싱은 제공하나 명확한 위치 기반 인덱싱이 사용되어야 하는 제약으로 인해 불린 인덱싱은 제공하지 않는다.

loc[]는 명칭 기반으로 데이터를 추출한다. 따라서 행 위치에는 DataFrame index 값을, 그리고 열 위치에는 칼럼 명을 입력해 준다. 단, loc[]에 슬라이싱 기호를 적용하면 종료 값-1이 아니라 종료 값까지 포함된다. 이는 명칭 기반 인덱싱의 특성 때문이다.

구분 loc iloc
탐색 대상 인덱스 이름(index label) 정수형 위치 인덱스(integer position)
범위 지정 가능(범위의 끝 포함)
ex) [‘a’:’c]->’a’, ‘b’, ‘c’
가능(범위의 끝 제외)
ex)[3:7]->3, 4, 5, 6(* 7 제외)

다음은 단일 행 인덱스를 이용하는 예시다.

print(df)
print('\n')

# 행 인덱스를 사용하여 행 1개 선택
label1 = df.loc['서준']
position1 = df.iloc[0]
print("df.loc['서준']:\n", label1)
print('\n')
print("df.iloc[0]:\n", position1)
    수학  영어   음악   체육
서준  90  98   85  100
우현  80  89   95   90
인아  70  95  100   90


df.loc['서준']:
 수학     90
영어     98
음악     85
체육    100
Name: 서준, dtype: int64


df.iloc[0]:
 수학     90
영어     98
음악     85
체육    100
Name: 서준, dtype: int64

loc 인덱서와 iloc 인덱서의 결과값에 차이가 없다.

다음은 2개 이상의 행 인덱스를 리스트 형태로 입력하는 예시로 매칭되는 모든 행 데이터를 동시에 추출한다.

# 행 인덱스를 사용하여 2개 이상의 행 선택
label2 = df.loc[['서준', '우현']]
position2 = df.iloc[[0, 1]]
print("df.loc[['서준', '우현']]:\n", label2)
print('\n')
print("df.iloc[[0, 1]]:\n", position2)
df.loc[['서준', '우현']]:
     수학  영어  음악   체육
서준  90  98  85  100
우현  80  89  95   90


df.iloc[[0, 1]]:
     수학  영어  음악   체육
서준  90  98  85  100
우현  80  89  95   90

다음은 행 인덱스의 범위를 지정하여 여러 개의 행을 동시에 선택하는 예시다.

# 행 인덱스의 범위를 지정하여 행 선택
label3 = df.loc['서준':'우현']
position3 = df.iloc[0:1]
print("df.loc['서준':'우현']:\n", label3)
print('\n')
print("df.iloc[0:1]:\n", position3)
df.loc['서준':'우현']:
     수학  영어  음악   체육
서준  90  98  85  100
우현  80  89  95   90


df.iloc[0:1]:
     수학  영어  음악   체육
서준  90  98  85  100

인덱스의 이름을 범위로 지정한 label3의 경우에는 범위의 마지막 값인 ‘우현’이 포함되지만, 정수형 위치 인덱스를 사용한 position3에는 범위의 마지막 값인 ‘우현’이 제외된다.

다음은 다양한 DataFrame의 값을 선택하는 예시다.

방식 설명
df[val] DataFrame에서 하나의 칼럼 또는 여러 칼럼을 선택한다. 편의를 위해 불리언 배열, 슬라이스, 불리언 DataFrame(어떤 기준에 근거해서 값을 대입해야 할 때)을 사용할 수 있다.
df.loc[val] DataFrame에서 라벨값으로 로우의 부분집합을 선택한다.
df.loc[:, val] DataFrame에서 라벨값으로 칼럼의 부분집합을 선택한다.
df.loc[val1, val2] DataFrame에서 라벨값으로 로우와 칼럼의 부분집합을 선택한다.
df.iloc[where] DataFrame에서 정수 인덱스로 로우의 부분집합을 선택한다.
df.iloc[:, where] DataFrame에서 정수 인덱스로 칼럼의 부분집합을 선택한다.
df.iloc[where_i, where_j] DataFrame에서 정수 인덱스로 로우와 칼럼의 부분집합을 선택한다.
df.at[label_i, label_j] 로우와 칼럼의 라벨로 단일 값을 선택한다.
df.iat[i, j] 로우와 칼럼의 정수 인덱스로 단일 값을 선택한다.
reindex 메서드 하나 이상의 축을 새로운 색인으로 맞춘다.
_get_value, _set_value 메서드 로우와 칼럼 이름으로 DataFrame의 값을 선택한다.

iloc, loc의 문제점과 주의할 점 정리

  1. 가장 중요한 것은 명칭 기반 인덱싱과 위치 기반 인덱싱의 차이를 이해하는 것이다. DataFrame의 인덱스나 칼럼명으로 데이터에 접근하는 것은 명칭 기반 인덱싱이다. 0부터 시작하는 행, 열의 위치 좌표에만 의존하는 것이 위치 기반 인덱싱이다.

  2. iloc[]는 위치 기반 인덱싱만 가능하다. 따라서 행과 열 위치 값으로 정수형 값을 지정해 원하는 데이터를 반환한다.

  3. loc[]는 명칭 기반 인덱싱만 가능하다. 따라서 행 위치에 DataFrame 인덱스가 오며, 열 위치에는 칼럼 명을 지정해 원하는 데이터를 반환한다.

  4. 명칭 기반 인덱싱에서 슬라이싱을 ‘시작점:종료점’으로 지정할 때 시작점에서 종료점을 포함한 위치에 있는 데이터를 반환한다.

열 선택

데이터프레임의 열 데이터를 1개만 선택할 때는, 대괄호([]) 안에 열 이름을 따옴표와 함께 입력하거나 도트(.)다음에 열 이름을 입력하는 두 가지 방식을 사용한다. 단, 두 번째 방법은 반드시 열 이름이 문자열이고 파이썬에서 사용 가능한 변수 이름 형식일 경우에만 가능하다. 이처럼 열 1개를 선택하면 시리즈 객체가 반환된다.

대괄호 안에 열 이름의 리스트를 입력하면 리스트의 원소인 열을 모두 선택하여 데이터프레임으로 반환한다. 또한 리스트의 원소로 열 이름 한 개만 있는 경우에도, 2중 대괄호([[]])를 사용하는 것이 되어 반환되는 객체는 시리즈가 아니라 데이터프레임이 된다.

# DataFrame() 함수로 데이터프레임 변환. 변수 df에 저장
exam_data = {'이름': ['서준', '우현', '인아'],
             '수학': [90, 80, 70],
             '영어': [98, 89, 95],
             '음악': [85, 95, 100],
             '체육': [100, 90, 90]}
df = pd.DataFrame(exam_data)
print(df)
print(type(df))
print('\n')

# '수학' 점수 데이터만 선택. 변수 math에 저장
math = df['수학']
print(math)
print(type(math))
print('\n')

# '영어' 점수 데이터만 선택. 변수 english에 저장
english = df.영어
print(english)
print(type(english))
   이름  수학  영어   음악   체육
0  서준  90  98   85  100
1  우현  80  89   95   90
2  인아  70  95  100   90
<class 'pandas.core.frame.DataFrame'>


0    90
1    80
2    70
Name: 수학, dtype: int64
<class 'pandas.core.series.Series'>


0    98
1    89
2    95
Name: 영어, dtype: int64
<class 'pandas.core.series.Series'>
# '음악', '체육' 점수 데이터를 선택. 변수 music_gym에 저장
music_gym = df[['음악', '체육']]
print(music_gym)
print(type(music_gym))
print('\n')

# '수학' 점수 데이터만 선택. 변수 math에 저장.
math = df[['수학']]
print(math)
print(type(math))
    음악   체육
0   85  100
1   95   90
2  100   90
<class 'pandas.core.frame.DataFrame'>


   수학
0  90
1  80
2  70
<class 'pandas.core.frame.DataFrame'>

원소 선택

데이터프레임의 행 인덱스와 열 이름을 [행, 열] 형식의 2차원 좌표로 입력하여 원소 위치를 지정하는 방법이다. 원소가 위치하는 행과 열의 좌표를 입력하면 해당 위치의 원소가 반환된다. 1개의 행과 2개 이상의 열을 선택하거나 반대로 2개 이상의 열과 1개의 열을 선택하는 경우 시리즈 객체가 반환된다. 2개 이상의 행과 2개 이상의 열을 선택하면, 데이터프레임 객체를 반환한다.

# '이름' 열을 새로운 인덱스로 지정하고, df 객체에 변경 사항 반영
df.set_index('이름', inplace=True)
print(df)
    수학  영어   음악   체육
이름                  
서준  90  98   85  100
우현  80  89   95   90
인아  70  95  100   90
# 데이터프레임 df의 특정 원소 1개 선택('서준'의 '음악' 점수)
a = df.loc['서준', '음악']
print(a)
b = df.iloc[0, 2]
print(b)
85
85
# 데이터프레임 df의 특정 원소 2개 이상 선택('서준'의 '음악', '체육' 점수)
c = df.loc['서준', ['음악', '체육']]
print(c)
d = df.iloc[0, [2, 3]]
print(d)
e = df.loc['서준', '음악':'체육']
print(e)
f = df.iloc[0, 2:]
음악     85
체육    100
Name: 서준, dtype: int64
음악     85
체육    100
Name: 서준, dtype: int64
음악     85
체육    100
Name: 서준, dtype: int64
# df 2개 이상의 행과 열에 속하는 원소들 선택('서준', '우현'의 '음악', '체육' 점수)
g = df.loc[['서준', '우현'], ['음악', '체육']]
print(g)
h = df.iloc[[0, 1], [2, 3]]
print(h)
i = df.loc['서준':'우현', '음악':'체육']
print(i)
j = df.iloc[0:2, 2:]
print(j)
    음악   체육
이름         
서준  85  100
우현  95   90
    음악   체육
이름         
서준  85  100
우현  95   90
    음악   체육
이름         
서준  85  100
우현  95   90
    음악   체육
이름         
서준  85  100
우현  95   90

열 추가

데이터프레임에 열을 추가하는 방법이다. “DataFrame 객체[‘추가하는 열 이름’] = 데이터 값” 이런 식으로 추가하려는 열 이름과 데이터 값을 입력한다. 데이터프레임의 마지막 열에 덧붙이듯 새로운 열을 추가한다. 이때 모든 행에 동일한 값이 적용된다.

print(df)
print('\n')

# 데이터프레임 df에 '국어' 점수 열(column) 추가. 데이터 값은 80 지정
df['국어'] = 80
print(df)
    수학  영어   음악   체육
이름                  
서준  90  98   85  100
우현  80  89   95   90
인아  70  95  100   90


    수학  영어   음악   체육  국어
이름                      
서준  90  98   85  100  80
우현  80  89   95   90  80
인아  70  95  100   90  80

리스트나 배열을 칼럼에 대입할 때는 대입하려는 값의 길이가 DataFrame의 크기와 동일해야 한다. Series를 대입하면 DataFrame의 색인에 따라 값이 대입되며 존재하지 않는 색인에는 결측치가 대입된다.

행 추가

데이터프레임에 행을 추가하는 방법이다. 추가하려는 행 이름과 데이터 값을 loc 인덱서를 사용하여 입력한다. 하나의 데이터 값을 입력하거나, 열의 개수에 맞게 배열 형태로 여러 개의 값을 입력할 수 있다. 전자의 경우 행의 모든 원소에 같은 값이 추가된다(브로드캐스팅). 후자의 경우 배열의 순서대로 열 위치에 값이 하나씩 추가된다. 또한 행 벡터 자체가 배열이므로, 기존 행을 복사해서 새로운 행에 그대로 추가할 수도 있다.

데이터프레임에 새로운 행을 추가할 때는 기존 행 인덱스와 겹치지 않는 새로운 인덱스를 사용한다. 기존 인덱스와 중복되는 경우 새로운 행을 추가하지 않고 개존 행의 원소값을 변경한다.

print(df)
print('\n')

# 새로운 행(row) 추가 - 같은 원소 값 입력
df.loc[3] = 0
print(df)
print('\n')

# 새로운 행(row) 추가 - 원소 값 여러 개의 배열 입력
df.loc[4] = ['동규', 90, 80, 70, 60]
print(df)
print('\n')

# 새로운 행(row) 추가 - 기존 행 복사
df.loc['행5'] = df.loc[3]
print(df)
    수학  영어   음악   체육  국어
이름                      
서준  90  98   85  100  80
우현  80  89   95   90  80
인아  70  95  100   90  80


    수학  영어   음악   체육  국어
이름                      
서준  90  98   85  100  80
우현  80  89   95   90  80
인아  70  95  100   90  80
3    0   0    0    0   0


    수학  영어   음악   체육  국어
이름                      
서준  90  98   85  100  80
우현  80  89   95   90  80
인아  70  95  100   90  80
3    0   0    0    0   0
4   동규  90   80   70  60


    수학  영어   음악   체육  국어
이름                      
서준  90  98   85  100  80
우현  80  89   95   90  80
인아  70  95  100   90  80
3    0   0    0    0   0
4   동규  90   80   70  60
행5   0   0    0    0   0

원소 값 변경

데이터프레임의 특정 원소를 선택하고 새로운 데이터 값을 지정해주면 원소 값이 변경된다. 원소 1개를 선택하여 변경할 수도 있고, 여러 개의 원소를 선택하여 한꺼번에 값을 바꿀 수도 있다. 변경할 원소를 선택할 때 데이터프레임 인덱싱과 슬라이싱 기법을 사용한다.

print(df)
print('\n')

# 데이터프레임 df의 특정 원소를 변경하는 방법: '서준'의 '체육' 점수
df.iloc[0][3] = 80
print(df)
print('\n')

df.loc['서준']['체육'] = 90
print(df)
print('\n')

df.loc['서준', '체육'] = 100
print(df)
    수학  영어   음악   체육
이름                  
서준  90  98   85  100
우현  80  89   95   90
인아  70  95  100   90


    수학  영어   음악  체육
이름                 
서준  90  98   85  80
우현  80  89   95  90
인아  70  95  100  90


    수학  영어   음악  체육
이름                 
서준  90  98   85  90
우현  80  89   95  90
인아  70  95  100  90


    수학  영어   음악   체육
이름                  
서준  90  98   85  100
우현  80  89   95   90
인아  70  95  100   90

여러 개의 원소를 선택하여 새로운 값을 할당하면, 모든 원소를 한꺼번에 같은 값으로 변경할 수 있다. 또한 선택한 원소의 개수에 맞춰 각기 다른 값을 배열 형태로 입력할 수도 있다.

# 데이터프레임 df의 원소 여러 개를 변경하는 방법: '서준'의 '음악', '체육' 점수
df.loc['서준', ['음악', '체육']] = 50
print(df)
print('\n')

df.loc['서준', ['음악', '체육']] = 100, 50
print(df)
    수학  영어   음악  체육
이름                 
서준  90  98   50  50
우현  80  89   95  90
인아  70  95  100  90


    수학  영어   음악  체육
이름                 
서준  90  98  100  50
우현  80  89   95  90
인아  70  95  100  90

행, 열의 위치 바꾸기

데이터프레임의 행과 열을 서로 맞바꾸는 방법이다. 선형대수학의 전치행렬과 같은 개념으로 NumPy에서 유래한 행렬 전치 메소드와 속성을 사용한다.

전치의 결과로 새로운 객체를 반환하므로, 기존 객체를 변경하기 위해서는 기존 객체에 새로운 객체를 할당해주는 과정이 필요하다.

print(df)
print('\n')

# 데이터프레임 df를 전치하기(메서드 활용)
df = df.transpose()
print(df)
print('\n')

# 데이터프레임 df를 다시 전치하기(클래스 속성 활용)
df = df.T
print(df)
   이름  수학  영어   음악   체육
0  서준  90  98   85  100
1  우현  80  89   95   90
2  인아  70  95  100   90


      0   1    2
이름   서준  우현   인아
수학   90  80   70
영어   98  89   95
음악   85  95  100
체육  100  90   90


   이름  수학  영어   음악   체육
0  서준  90  98   85  100
1  우현  80  89   95   90
2  인아  70  95  100   90

인덱스 활용

특정 열을 행 인덱스로 설정

set_index() 메서드를 사용하여 데이터프레임의 특정 열을 행 인덱스로 설정한다. 단, 원본 데이터프레임을 바꾸지 않고 새로운 데이터프레임 객체를 반환하는 점에 유의한다. 따라서 원본 데이터프레임에 변경 사항을 적용하려면 df = df.set_index([‘특정 열’])와 같이 원래 변수에 할당하거나, df.set_index([‘특정 열’], inplace=True)와 같이 inplace 옵션을 추가해야 한다.

한편 set_index() 메서드를 사용하여 행 인덱스를 새로 지정하면 기존 행 인덱스는 삭제된다.

# DataFrame() 함수로 데이터프레임 변환. 변수 df에 저장
exam_data = {'이름': ['서준', '우현', '인아'],
             '수학': [90, 80, 70],
             '영어': [98, 89, 95],
             '음악': [85, 95, 100],
             '체육': [100, 90, 90]}
df = pd.DataFrame(exam_data)
print(df)
print('\n')

# 특정 열(column)을 데이터프레임의 행 인덱스로(index)로 설정
ndf = df.set_index(['이름']) # df의 '이름' 열을 행 인덱스로 설정
print(ndf)
print('\n')
ndf2 = ndf.set_index('음악') # ndf의 '음악' 열을 행 인덱스로 지정하여 생성
print(ndf2)
print('\n')
ndf3 = ndf.set_index(['수학', '음악']) # 2개의 열을 행 인덱스로 지정한 경우로, 멀티인덱스(MultiIndex)라고 한다.
print(ndf3)
   이름  수학  영어   음악   체육
0  서준  90  98   85  100
1  우현  80  89   95   90
2  인아  70  95  100   90


    수학  영어   음악   체육
이름                  
서준  90  98   85  100
우현  80  89   95   90
인아  70  95  100   90


     수학  영어   체육
음악              
85   90  98  100
95   80  89   90
100  70  95   90


        영어   체육
수학 음악          
90 85   98  100
80 95   89   90
70 100  95   90

행 인덱스와 칼럼 재배열

reindex() 메서드를 사용하면 데이터프레임의 행 인덱스를 새로운 배열로 재지정할 수 있다. 기존 객체를 변경하지 않고 새로운 데이터프레임 객체를 반환한다.

기존 데이터프레임에 존재하지 않는 행 인덱스가 새롭게 추가되는 경우 그 행의 데이터 값은 NaN2 값이 입력된다. 이럴 경우 데이터가 존재하지 않는다는 뜻의 NaN 대신 유효한 값으로 채우려면 fill_value 옵션에 원하는 값을 입력한다.

# 딕셔너리 정의
dict_data = {'c0':[1,2,3], 'c1':[4,5,6], 'c2':[7,8,9], 'c3':[10,11,12], 'c4':[13,14,15]}

# 딕셔너리를 데이터프레임으로 변환. 인덱스를 [r0, r1, r2]로 지정
df = pd.DataFrame(dict_data, index=['r0', 'r1', 'r2'])
print(df)
print('\n')

# 인덱스를 [r0, r1, r2, r3, r4]로 재지정
new_index = ['r0', 'r1', 'r2', 'r3', 'r4']
ndf = df.reindex(new_index)
print(ndf)
print('\n')

# reindex로 발생한 NaN 값을 숫자 0으로 채우기
new_index = ['r0', 'r1', 'r2', 'r3', 'r4']
ndf2 = df.reindex(new_index, fill_value=0) # NaN 값을 0으로 채움
print(ndf2)
    c0  c1  c2  c3  c4
r0   1   4   7  10  13
r1   2   5   8  11  14
r2   3   6   9  12  15


     c0   c1   c2    c3    c4
r0  1.0  4.0  7.0  10.0  13.0
r1  2.0  5.0  8.0  11.0  14.0
r2  3.0  6.0  9.0  12.0  15.0
r3  NaN  NaN  NaN   NaN   NaN
r4  NaN  NaN  NaN   NaN   NaN


    c0  c1  c2  c3  c4
r0   1   4   7  10  13
r1   2   5   8  11  14
r2   3   6   9  12  15
r3   0   0   0   0   0
r4   0   0   0   0   0

시계열 같은 순차적인 데이터를 재색인할 때 값을 보간하거나 채워 넣어야 할 경우가 있다. method 옵션을 이용해서 이를 해결할 수 있으며, ffill 같은 메서드를 이용해서 누락된 값을 직전의 값으로 채워 넣을 수 있다.

obj = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])

print(obj)
print('\n')

# reindex로 발생한 NaN 값을 직전의 값으로 채우기
nobj = obj.reindex(range(6), method='ffill')
print(nobj)
0      blue
2    purple
4    yellow
dtype: object


0      blue
1      blue
2    purple
3    purple
4    yellow
5    yellow
dtype: object

DataFrame에 대한 reindex는 로우(인덱스), 칼럼 또는 둘 다 변경 가능하다. 그냥 순서만 전달하면 인덱스가 재배열된다.

frame = pd.DataFrame(np.arange(9).reshape((3, 3)),
                     index=['a', 'c', 'd'],
                     columns=['Ohio', 'Texas', 'California'])

print(frame)
print('\n')

frame2 = frame.reindex(['a', 'b', 'c', 'd'])
print(frame2)
   Ohio  Texas  California
a     0      1           2
c     3      4           5
d     6      7           8


   Ohio  Texas  California
a   0.0    1.0         2.0
b   NaN    NaN         NaN
c   3.0    4.0         5.0
d   6.0    7.0         8.0

칼럼은 columns 예약어를 사용해서 인덱스 재배열을 할 수 있다.

states = ['Texas', 'Utah', 'California']

frame3 = frame.reindex(columns=states) # 기존에 있던 'Ohio'는 사라지고 NaN 값으로 채워진 'Utah' 열이 생성
print(frame3)
   Texas  Utah  California
a      1   NaN           2
c      4   NaN           5
d      7   NaN           8

다음은 인덱스 재배열 함수 인자다.

인자 설명
index 인덱스로 사용할 새로운 순서. Index 인스턴스나 다른 순차적인 자료구조가 사용 가능하다. Index는 복사가 이루어지지 않고 그대로 사용된다.
method 채움 메서드. ffill은 직전 값을 채워 넣고 bfill은 다음 값을 채워 넣는다.
fill_value 재배열 과정 중에 새롭게 나타나는 비어 있는 값을 채우기 위한 값
limit 전/후 보간 시에 사용할 최대 갭 크기(채워넣을 원소의 수)
tolerance 전/후 보간 시에 사용할 최대 갭 크기(값의 차이)
level MultiIndex의 단계(level)에 단순 인덱스를 맞춘다. 그렇지 않으면 MultiIndex의 하위집합에 맞춘다.
copy True인 경우 새로운 인덱스가 이전 인덱스와 동일하더라도 데이터를 복사한다. False인 경우 새로운 인덱스가 이전 인덱스와 동일할 경우 복사하지 않는다.

행 인덱스 초기화

reset_index() 메서드를 활용하여 행 인덱스를 정수형 위치 초기화한다. 이때 기존 행 인덱스는 ‘index’라는 새로운 칼럼 명으로 추가된다. 다른 경우와 마찬가지로 새로운 데이터프레임 객체를 반환한다.

reset_index()는 인덱스가 연속된 int 숫자형 데이터가 아닐 경우에 다시 이를 연속 int 숫자형 데이터로 만들 때 주로 사용한다. 또한 reset_index()의 parameter 중 drop=True로 설정하면 기존 인덱스는 새로운 칼럼으로 추가되지 않고 삭제(drop)된다.

print(df)
print('\n')

# 행 인덱스를 정수형으로 초기화
ndf = df.reset_index()
print(ndf)
    c0  c1  c2  c3  c4
r0   1   4   7  10  13
r1   2   5   8  11  14
r2   3   6   9  12  15


  index  c0  c1  c2  c3  c4
0    r0   1   4   7  10  13
1    r1   2   5   8  11  14
2    r2   3   6   9  12  15
print(df)
print('\n')

# 행 인덱스를 정수형으로 초기화하되, 기존 인덱스는 삭제
ndf2 = df.reset_index(drop=True)
print(ndf2)
    c0  c1  c2  c3  c4
r0   1   4   7  10  13
r1   2   5   8  11  14
r2   3   6   9  12  15


   c0  c1  c2  c3  c4
0   1   4   7  10  13
1   2   5   8  11  14
2   3   6   9  12  15

행 인덱스와 칼럼을 기준으로 데이터프레임 정렬

sort_index() 메서드를 활용하여 행 인덱스를 기준으로 데이터프레임의 값을 정렬한다. ascending 옵션을 사용하여 오름차순 또는 내림차순을 설정한다. 새롭게 정렬된 데이터프레임을 반환한다.

print(df)
print('\n')

# 내림차순으로 행 인덱스 정렬
ndf = df.sort_index(ascending=False)
print(ndf)
    c0  c1  c2  c3  c4
r0   1   4   7  10  13
r1   2   5   8  11  14
r2   3   6   9  12  15


    c0  c1  c2  c3  c4
r2   3   6   9  12  15
r1   2   5   8  11  14
r0   1   4   7  10  13

DataFrame은 로우나 칼럼 중 하나의 축을 기준으로 정렬할 수 있다.

print(df)
print('\n')

# 내림차순으로 칼럼 정렬
ndf = df.sort_index(axis=1, ascending=False)
print(ndf)
    c0  c1  c2  c3  c4
r0   1   4   7  10  13
r1   2   5   8  11  14
r2   3   6   9  12  15


    c4  c3  c2  c1  c0
r0  13  10   7   4   1
r1  14  11   8   5   2
r2  15  12   9   6   3

특정 열의 데이터 값을 기준으로 데이터프레임 정렬하기

특정 열의 데이터를 기준으로 데이터프레임을 정렬할 수 있다. sort_values() 메서드를 활용하며, 새롭게 정렬된 데이터프레임 객체를 반환한다.

print(df)
print('\n')

# c1 열을 기준으로 내림차순 정렬
ndf = df.sort_values(by='c1', ascending=False)
print(ndf)
    c0  c1  c2  c3  c4
r0   1   4   7  10  13
r1   2   5   8  11  14
r2   3   6   9  12  15


    c0  c1  c2  c3  c4
r2   3   6   9  12  15
r1   2   5   8  11  14
r0   1   4   7  10  13

여러 개의 칼럼을 정렬하려면 칼럼 이름이 담긴 리스트를 전달하면 된다.

frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]})

print(frame)
print('\n')

# a 와 b 열을 기준으로 a 는 오름차순, 동일한 a 값에 대해서 b 는 내림차순으로 정렬
nframe = frame.sort_values(by=['a', 'b'], ascending=[True, False])
print(nframe)
   b  a
0  4  0
1  7  1
2 -3  0
3  2  1


   b  a
0  4  0
2 -3  0
1  7  1
3  2  1

순위

순위는 정렬과 거의 흡사한데, 1부터 배열의 유효한 데이터 개수까지 순서를 매긴다. 기본적으로 Series와 DataFrame의 rank 메서드는 동점인 항목에 대해서는 평균 순위를 매긴다.

obj = pd.Series([7, -5, 7, 4, 2, 0, 4])

obj.rank()
0    6.5
1    1.0
2    6.5
3    4.5
4    3.0
5    2.0
6    4.5
dtype: float64

데이터 상에서 나타나는 순서에 따라 순위를 매길 수도 있다.

obj.rank(method='first') # 0번째와 2번째 항목에 대해 평균 순위인 6.5가 아닌 출현한 순서대로 6과 7을 적용
0    6.0
1    1.0
2    7.0
3    4.0
4    3.0
5    2.0
6    5.0
dtype: float64

내림차순으로 순위를 매길 수도 있다.

# 동률인 경우 그룹 내에서 높은 순위를 적용한다.
obj.rank(ascending=False, method='max')
0    2.0
1    7.0
2    2.0
3    4.0
4    5.0
5    6.0
6    4.0
dtype: float64

DataFrame에서는 로우나 칼럼에 대해 순위를 정할 수 있다.

frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1],
                      'c': [-2, 5, 8, -2.5]}, columns=['a', 'b', 'c'])
print(frame)
print('\n')

# 칼럼에 대해 순위를 정함
print(frame.rank(axis='columns'))
   a    b    c
0  0  4.3 -2.0
1  1  7.0  5.0
2  0 -3.0  8.0
3  1  2.0 -2.5


     a    b    c
0  2.0  3.0  1.0
1  1.0  3.0  2.0
2  2.0  1.0  3.0
3  2.0  3.0  1.0

다음은 순위의 동률을 처리하는 메서드이다.

메서드 설명
‘average’ 기본값. 같은 값을 가지는 항목들의 평균값을 순위로 삼는다.
‘min’ 같은 값을 가지는 그룹을 낮은 순위로 매긴다.
‘max’ 같은 값을 가지는 그룹을 높은 순위로 매긴다.
‘first’ 데이터 내의 위치에 따라 순위를 매긴다.
‘dense’ method=’min’과 같지만 같은 그룹 내에서 모두 같은 순위를 적용하지 않고 1씩 증가시킨다.

산술연산

판다스 객체의 산술연산은 내부적으로 3단계 프로세스를 거친다.

  1. 행/열 인덱스를 기준으로 모든 원소를 정렬한다.
  2. 동일한 위치에 있는 원소끼리 일대일로 대응시킨다.
  3. 일대일 대응이 되는 원소끼리 연산을 처리한다.

    단, 이때 대응되는 원소가 없으면 결과에 두 인덱스가 통합되며 NaN으로 처리한다.

데이터베이스의 외부 조인과 유사하게 동작한다고 생각할 수 있다.

시리즈 연산

시리즈 vs 숫자

시리즈 객체에 어떤 숫자를 더하면 시리즈의 개별 원소에 각각 숫자를 더하고 계산한 겨로가를 시리즈 객체로 반환한다. 덧셈, 뺄셈, 곱셈, 나눗셈 모두 가능하다. 다음은 시리즈 객체의 각 원소를 200으로 나누는 예제다.

# 딕셔너리 데이터로 판다스 시리즈 만들기
student1 = pd.Series({'국어':100, '영어':80, '수학':90})
print(student1)
print('\n')

# 학생의 과목별 점수를 200으로 나누기
percentage = student1 / 200

# 연산 결과를 원래 인덱스와 동일한 '국어', '영어', '수학' 인덱스에 순서대로 매칭하여 
# 시리즈를 반환
print(percentage) 
print('\n')
print(type(percentage))
국어    100
영어     80
수학     90
dtype: int64


국어    0.50
영어    0.40
수학    0.45
dtype: float64


<class 'pandas.core.series.Series'>

시리즈 vs 시리즈

시리즈와 시리즈 사이에 사칙연산을 처리하는 방법이다. 시리즈의 모든 인덱스에 대하여 같은 인덱스를 가진 원소끼리 계산한다. 인덱스에 연산 결과를 매칭하여 새 시리즈를 반환한다.

# 딕셔너리 데이터로 판다스 시리즈 만들기
student1 = pd.Series({'국어':100, '영어':80, '수학':90})
student2 = pd.Series({'수학':80, '국어':90, '영어':80})

print(student1)
print('\n')
print(student2)
print('\n')

# 두 학생의 과목별 점수로 사칙연산 수행
addition = student1 + student2                   # 덧셈
subtraction = student1 - student2                # 뺄셈
multiplication = student1 * student2             # 곱셈
division = student1 / student2                   # 나눗셈
print(type(division))
print('\n')

# 사칙연산 결과를 데이터프레임으로 합치기(시리즈 -> 데이터프레임)
result = pd.DataFrame([addition, subtraction, multiplication, division],
                      index=['덧셈', '뺄셈', '곱셈', '나눗셈'])
print(result)
국어    100
영어     80
수학     90
dtype: int64


수학    80
국어    90
영어    80
dtype: int64


<class 'pandas.core.series.Series'>


              국어        수학      영어
덧셈    190.000000   170.000   160.0
뺄셈     10.000000    10.000     0.0
곱셈   9000.000000  7200.000  6400.0
나눗셈     1.111111     1.125     1.0

앞의 예제에서 인덱스로 주어진 과목명의 순서가 다르지만, 판다스는 같은 과목명(인덱스)을 찾아 정렬한 후 같은 과목명(인덱스)의 점수(데이터 값)끼리 덧셈을 한다. 덧셈의 결과를 과목명(인덱스)에 매칭시키고 새로운 시리즈 객체를 반환한다.

연산을 하는 두 시리즈의 원소 개수가 다르거나, 시리즈의 크기가 같더라도 인덱스 값이 다를 수 있다. 이처럼 어느 한쪽에만 인덱스가 존재하고 다른 쪽에는 짝을 지을 수 있는 동일한 인덱스가 없는 경우 정상적으로 연산을 처리할 수 없다. 판다스는 유효한 값이 존재하지 않는다는 의미를 갖는 NaN으로 처리한다. 따라서 연산의 결과 또한 NaN으로 입력된다.

한편 동일한 인덱스가 양쪽에 모두 존재하여 서로 대응되더라도 어느 한 쪽의 데이터 값이 NaN인 경우가 있다. 이때도 연산의 대상인 데이터가 존재하지 않기 때문에 결과는 NaN이 된다.

# 딕셔너리 데이터로 판다스 시리즈 만들기
student1 = pd.Series({'국어':np.nan, '영어':80, '수학':90})
student2 = pd.Series({'수학':80, '국어':90})

print(student1)
print('\n')
print(student2)
print('\n')

# 두 학생의 과목별 점수로 사칙연산 수행(시리즈 vs 시리즈)
addition = student1 + student2                   # 덧셈
subtraction = student1 - student2                # 뺄셈
multiplication = student1 * student2             # 곱셈
division = student1 / student2                   # 나눗셈
print(type(division))
print('\n')

# 사칙연산 결과를 데이터프레임으로 합치기(시리즈 -> 데이터프레임)
result = pd.DataFrame([addition, subtraction, multiplication, division],
                      index=['덧셈', '뺄셈', '곱셈', '나눗셈'])
print(result)
국어     NaN
영어    80.0
수학    90.0
dtype: float64


수학    80
국어    90
dtype: int64


<class 'pandas.core.series.Series'>


     국어        수학  영어
덧셈  NaN   170.000 NaN
뺄셈  NaN    10.000 NaN
곱셈  NaN  7200.000 NaN
나눗셈 NaN     1.125 NaN

연산 메서드

객체 사이에 공통 인덱스가 없거나 NaN이 포함된 경우 연산 결과는 NaN으로 반환된다. 이런 상황을 피하려면 연산 메서드에 fill_value 옵션을 설정하여 적용한다.

다음은 산술 연산 메서드이다. 각각의 산술 연산 메서드는 r로 시작하는 계산 인자를 뒤집어 계산하는 짝궁 메서드를 가진다.

메서드 설명
add, radd 덧셈(+)을 위한 메서드
sub, rsub 뺄셈(-)을 위한 메서드
div, rdiv 나눗셈(/)을 위한 메서드
floordiv, rfloordiv 소수점 내림(//)연산을 위한 메서드
mul, rmul 곱셈(*)을 위한 메서드
pow, rpow 멱승(**)을 위한 메서드
print(student1)
print('\n')
print(student2)
print('\n')

# 두 학생의 과목별 점수로 사칙연산 수행(연산 메서드 사용)
sr_add = student1.add(student2, fill_value=0)          # 덧셈
sr_sub = student1.sub(student2, fill_value=0)          # 뺄셈
sr_mul = student1.mul(student2, fill_value=0)          # 곱셈
sr_div = student1.div(student2, fill_value=0)          # 나눗셈

# 사칙연산 결과를 데이터프레임으로 합치기(시리즈 -> 데이터프레임)
result = pd.DataFrame([sr_add, sr_sub, sr_mul, sr_div],
                      index=['덧셈', '뺄셈', '곱셈', '나눗셈'])
print(result)
국어     NaN
영어    80.0
수학    90.0
dtype: float64


수학    80
국어    90
dtype: int64


       국어        수학    영어
덧셈   90.0   170.000  80.0
뺄셈  -90.0    10.000  80.0
곱셈    0.0  7200.000   0.0
나눗셈   0.0     1.125   inf

데이터프레임 연산

데이터프레임은 여러 시리즈가 한데 모인 것이므로 시리즈 연산을 확장하는 개념으로 이해하는 것이 좋다. 먼저 행/열 인덱스를 기준으로 정렬하고 일대일 대응되는 원소끼리 연산을 처리한다.

데이터프레임 vs 숫자

데이터프레임에 어떤 숫자를 더하면 모든 원소에 숫자를 더한다. 덧셈, 뺄셈, 곱셈, 나눗셈 모두 가능하다. 기존 데이터프레임의 형태를 그대로 유지한 채 원소 값만 새로운 계산값으로 바뀐다. 새로운 데이터프레임 객체로 반환되는 점에 유의한다.

다음의 예제는 데이터프레임에 10을 더하는 과정을 설명한다. 여기서는 Seaborn 라이브러리에서 제공하는 데이터셋3 중에서 타이타닉(‘titanic’) 데이터셋을 사용한다. 타이타닉호 탑승자에 대한 인적사항과 구조 여부 등을 정리한 자료이다. load_dataset() 함수로 불러온다.

# 라이브러리 불러오기
import pandas as pd
import seaborn as sns

# titanic 데이터셋에서 age, fare 2개 열을 선택하여 데이터프레임 만들기
titanic = sns.load_dataset('titanic')
df = titanic.loc[:, ['age', 'fare']]
print(df.head())     # 첫 5행만 표시
print('\n')
print(type(df))
print('\n')

# 데이터프레임에 숫자 10 더하기
addition = df + 10     # 모든 원소에 숫자 10을 더하고 데이터프레임의 크기와 모양은 변하지 않는다.
print(addition.head()) # 첫 5행만 표시
print('\n')
print(type(addition))
    age     fare
0  22.0   7.2500
1  38.0  71.2833
2  26.0   7.9250
3  35.0  53.1000
4  35.0   8.0500


<class 'pandas.core.frame.DataFrame'>


    age     fare
0  32.0  17.2500
1  48.0  81.2833
2  36.0  17.9250
3  45.0  63.1000
4  45.0  18.0500


<class 'pandas.core.frame.DataFrame'>

데이터프레임 vs 데이터프레임

각 데이터프레임의 같은 행, 같은 열 위치에 있는 원소끼리 계산한다. 이처럼 동일한 위치의 원소끼리 계산한 결과값을 원래 위치에 다시 입력하여 데이터프레임을 만든다. 데이터프레임 중에서 어느 한쪽에 원소가 존재하지 않거나 NaN이면 연산 결과는 NaN으로 처리된다.

다음의 예제는 데이터프레임(df)에 숫자 10을 더해서 데이터프레임(addition)을 만든다. 그리고 덧셈의 결과인 데이터프레임(addition)에서 원래 데이터프레임(df)을 빼면, 숫자 10을 원소로만 갖는 데이터프레임(subtraction)이 반환된다. 단, NaN이 포함된 경우 NaN으로 처리된다.

print(df.tail()) # 마지막 5행 표시
print('\n')
print(type(df))
print('\n')

# 데이터프레임에 숫자 10 더하기
addition = df + 10
print(addition.tail()) # 마지막 5행 표시
print('\n')
print(type(addition))
print('\n')

# 데이터프레임끼리 연산하기(addition - df)
subtraction = addition - df
print(subtraction.tail()) # 마지막 5행 표시
print('\n')
print(type(subtraction))
      age   fare
886  27.0  13.00
887  19.0  30.00
888   NaN  23.45
889  26.0  30.00
890  32.0   7.75


<class 'pandas.core.frame.DataFrame'>


      age   fare
886  37.0  23.00
887  29.0  40.00
888   NaN  33.45
889  36.0  40.00
890  42.0  17.75


<class 'pandas.core.frame.DataFrame'>


      age  fare
886  10.0  10.0
887  10.0  10.0
888   NaN  10.0
889  10.0  10.0
890  10.0  10.0


<class 'pandas.core.frame.DataFrame'>

서로 겹치는 인덱스가 없는 경우 데이터는 NaN 값이 된다. 산술 연산 시 누락된 값은 전파된다.

데이터프레임의 경우 정렬은 로우와 칼럼 모두에 적용된다.

df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'),
                   index=['Ohio', 'Texas', 'Colorado'])
df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'),
                   index=['Utah', 'Ohio', 'Texas', 'Oregon'])

print(df1)
print('\n')
print(df2)
print('\n')

print(df1 + df2) # 각 DataFrame에 있는 인덱스와 칼럼이 하나로 합쳐진다.
            b    c    d
Ohio      0.0  1.0  2.0
Texas     3.0  4.0  5.0
Colorado  6.0  7.0  8.0


          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0


            b   c     d   e
Colorado  NaN NaN   NaN NaN
Ohio      3.0 NaN   6.0 NaN
Oregon    NaN NaN   NaN NaN
Texas     9.0 NaN  12.0 NaN
Utah      NaN NaN   NaN NaN

공통되는 칼럼 라벨이나 로우 라벨이 없는 데이터프레임을 더하면 결과에 아무것도 나타나지 않는다.

df1 = pd.DataFrame({'A': [1, 2]})
df2 = pd.DataFrame({'B': [3, 4]})

print(df1)
print('\n')
print(df2)
print('\n')

print(df1 + df2)
   A
0  1
1  2


   B
0  3
1  4


    A   B
0 NaN NaN
1 NaN NaN

이에 앞서 보았던 연산 메서드를 적용하면 NaN 값을 원하는 값으로 채우거나 보간할 수 있다.

데이터프레임 vs 시리즈

다른 차원의 NumPy 배열과의 연산처럼 데이터프레임과 시리즈 간의 연산도 잘 정의되어 있다. 먼저 2차원 배열과 그 배열의 한 로우의 차이에 대해 생각할 수 있는 예제를 살펴본다.

arr = np.arange(12.).reshape((3, 4))

print(arr.shape)
print(arr)
print('\n')
print(arr[0].shape)
print(arr[0])
print('\n')

print((arr - arr[0]).shape)
print(arr - arr[0])
(3, 4)
[[ 0.  1.  2.  3.]
 [ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]]


(4,)
[0. 1. 2. 3.]


(3, 4)
[[0. 0. 0. 0.]
 [4. 4. 4. 4.]
 [8. 8. 8. 8.]]

arr에서 arr[0]을 빼면 계산은 각 로우에 대해 한 번씩만 수행된다. 이를 브로드캐스팅이라고 하는데 더 자세한 내용은 다음을 참고한다. 데이터프레임과 시리즈 간의 연산은 이와 유사하다.

frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
                     columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series = frame.iloc[0]

print(frame)
print('\n')
print(series)
print('\n')
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0


b    0.0
d    1.0
e    2.0
Name: Utah, dtype: float64

기본적으로 데이터프레임과 시리즈 간의 산술 연산은 시리즈의 인덱스를 데이터프레임의 칼럼에 맞추고 아래 로우로 전파한다.

print(frame - series)
          b    d    e
Utah    0.0  0.0  0.0
Ohio    3.0  3.0  3.0
Texas   6.0  6.0  6.0
Oregon  9.0  9.0  9.0

만약 인덱스값을 데이터프레임의 칼럼이나 시리즈의 색인에서 찾을 수 없다면 그 객체는 형식을 맞추기 위해 인덱스가 재배치된다.

series2 = pd.Series(range(3), index=['b', 'e', 'f'])

print(series2)
print('\n')
print(frame)
print('\n')

print(frame + series2)
b    0
e    1
f    2
dtype: int64


          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0


          b   d     e   f
Utah    0.0 NaN   3.0 NaN
Ohio    3.0 NaN   6.0 NaN
Texas   6.0 NaN   9.0 NaN
Oregon  9.0 NaN  12.0 NaN

만약 각 로우에 대해 연산을 수행하고 싶다면 산술 연산 메서드를 사용하면 된다.

series3 = frame['d']

print(frame)
print('\n')
print(series3)
print('\n')

print(frame.sub(series3, axis='index'))
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0


Utah       1.0
Ohio       4.0
Texas      7.0
Oregon    10.0
Name: d, dtype: float64


          b    d    e
Utah   -1.0  0.0  1.0
Ohio   -1.0  0.0  1.0
Texas  -1.0  0.0  1.0
Oregon -1.0  0.0  1.0

인자로 넘기는 axis 값은 연산을 적용할 축 번호다. axis=’index’나 axis=0은 데이터프레임의 로우를 따라 연산을 수행하라는 의미다.

  1. 원자료(raw data)를 보다 쉽게 접근하고 분석할 수 있도록 데이터를 통합하고 정리하는 과정 

  2. NaN은 “Not a Number”라는 뜻으로, 유효한 값이 존재하지 않는 누락 데이터를 말한다. 

  3. Seaborn 내장 데이터셋의 종류: ‘anscombe’, ‘attention’, ‘brain_networks’, ‘car_crashes’, ‘diamonds’, ‘dots’, ‘exercise’, ‘flights’, ‘fmri’, ‘iris’, ‘mpg’, ‘planets’, ‘tips’, ‘titanic’ 

댓글남기기