소개
Pandas는 인덱스 위치(index position) 또는 인덱스 레이블(index label)을 사용해 데이터의 부분 집합을 선택할 수 있는 이중 선택 기능을 제공합니다. 이번 글에서는 그중에서도 "사전식(lexicographical) 슬라이싱"을 활용해 데이터의 부분 집합을 선택하는 방법을 소개합니다.
인터넷에는 방대한 양의 데이터셋이 공개되어 있습니다. kaggle.com에서 'movies dataset'을 검색해 보세요. 이 글 역시 캐글(Kaggle)에서 제공하는 영화 데이터셋을 사용합니다.
실습 방법
1단계. 이 예제에 필요한 컬럼만 골라서 영화 데이터셋을 불러옵니다.
import pandas as pd
import numpy as np
movies = pd.read_csv("https://raw.githubusercontent.com/sasankac/TestDataSet/master/movies_data.csv",index_col="title",
usecols=["title","budget","vote_average","vote_count"])
movies.sample(n=5)| title | budget | vote_average | vote_count |
|---|---|---|---|
| Little Voice | 0 | 6.6 | 61 |
| Grown Ups 2 | 80000000 | 5.8 | 1155 |
| The Best Years of Our Lives | 2100000 | 7.6 | 143 |
| Tusk | 2800000 | 5.1 | 366 |
| Operation Chromite | 0 | 5.8 | 29 |
2단계. 인덱스를 항상 정렬해 두는 것을 권장합니다. 특히 인덱스가 문자열로 구성된 경우라면 더욱 그렇습니다. 대용량 데이터셋을 다룰 때 인덱스가 정렬되어 있으면 성능 차이를 실감하게 됩니다.
인덱스를 정렬하지 않으면 어떻게 될까?
걱정하지 마세요. 코드가 영원히 실행되는 것은 아닙니다. 물론 농담이지만, 실제로 인덱스 레이블이 정렬되어 있지 않으면 Pandas는 쿼리와 일치하는 값을 찾기 위해 모든 레이블을 하나씩 처음부터 끝까지 순회해야 합니다. 색인 페이지가 없는 옥스포드 사전을 상상해 보세요. 원하는 단어를 찾으려면 어떻게 해야 할까요? 반면 인덱스가 정렬되어 있다면 원하는 레이블로 즉시 이동할 수 있습니다. Pandas도 마찬가지입니다.
먼저 현재 인덱스가 정렬되어 있는지 확인해 보겠습니다.
# check if the index is sorted or not ? movies.index.is_monotonic False
3단계. 확인 결과 인덱스가 정렬되어 있지 않습니다. 이제 'A'로 시작하는 영화들을 선택하려고 시도해 보겠습니다. SQL로 표현하면 다음과 같은 쿼리와 같습니다.
select * from movies where title like 'A%'
movies.loc["Aa":"Bb"]
---------------------------------------------------------------------------
ValueErrorTraceback (most recent call last)
~\anaconda3\lib\site-packages\pandas\core\indexes\base.py in get_slice_bound(self, label, side, kind)
-> raise ValueError("index must be monotonic increasing or decreasing")
ValueError: index must be monotonic increasing or decreasing
During handling of the above exception, another exception occurred:
KeyErrorTraceback (most recent call last)
----> 1 movies.loc["Aa": "Bb"]
...
pandas\_libs\index.pyx in pandas._libs.index.IndexEngine._maybe_get_bool_indexer()
KeyError: 'Aa'인덱스가 단조 증가(monotonic increasing)하지 않아 KeyError가 발생하며 슬라이싱에 실패했습니다.
4단계. 인덱스를 오름차순으로 정렬한 뒤 동일한 명령을 다시 실행하면, 정렬의 이점을 활용한 사전식 슬라이싱이 가능해집니다.
movies = movies.sort_index() movies.index.is_monotonic True
5단계. 이제 데이터가 준비되었으므로 본격적으로 사전식 슬라이싱을 수행할 수 있습니다. 영화 제목이 'A'로 시작하는 것부터 'B'로 시작하는 것까지 전부 선택해 보겠습니다.
movies.loc["A":"B"]
| title | budget | vote_average | vote_count |
|---|---|---|---|
| Abandon | 25000000 | 4.6 | 45 |
| Abandoned | 0 | 5.8 | 27 |
| Abduction | 35000000 | 5.6 | 961 |
| Aberdeen | 0 | 7.0 | 6 |
| About Last Night | 12500000 | 6.0 | 210 |
| ... | ... | ... | ... |
| Battle for the Planet of the Apes | 1700000 | 5.5 | 215 |
| Battle of the Year | 20000000 | 5.9 | 88 |
| Battle: Los Angeles | 70000000 | 5.5 | 1448 |
| Battlefield Earth | 44000000 | 3.0 | 255 |
| Battleship | 209000000 | 5.5 | 2114 |
'Aa'부터 'Bb' 사이의 모든 영화가 정상적으로 추출되었습니다.
주의: 특수문자와 대소문자 처리
사전식 슬라이싱은 유니코드 순서를 따르기 때문에 특수문자나 대소문자가 섞인 제목은 의외의 위치에 배치될 수 있습니다.
| title | budget | vote_average | vote_count |
|---|---|---|---|
| Æon Flux | 62000000 | 5.4 | 703 |
| xXx: State of the Union | 60000000 | 4.7 | 549 |
| xXx | 70000000 | 5.8 | 1424 |
| eXistenZ | 15000000 | 6.7 | 475 |
| [REC]² | 5600000 | 6.4 | 489 |
위 결과처럼 'Æ', 'x', 'e', '[' 등의 문자는 일반적인 알파벳 순서와 다르게 정렬되므로, 슬라이싱 범위를 지정할 때 주의가 필요합니다.
내림차순 정렬 데이터에서의 슬라이싱
데이터가 내림차순으로 정렬되어 있는 상태에서 오름차순 기준의 슬라이싱을 수행하면 어떻게 될까요? 당연하게도 빈 DataFrame이 반환됩니다. 문자 범위의 순서를 반대로 지정해 다시 실행해 보겠습니다.
| title | budget | vote_average | vote_count |
|---|---|---|---|
| B-Girl | 0 | 5.5 | 7 |
| Ayurveda: Art of Being | 300000 | 5.5 | 3 |
| Away We Go | 17000000 | 6.7 | 189 |
| Awake | 86000000 | 6.3 | 395 |
| Avengers: Age of Ultron | 280000000 | 7.3 | 6767 |
| ... | ... | ... | ... |
| About Last Night | 12500000 | 6.0 | 210 |
| Aberdeen | 0 | 7.0 | 6 |
| Abduction | 35000000 | 5.6 | 961 |
| Abandoned | 0 | 5.8 | 27 |
| Abandon | 25000000 | 4.6 | 45 |
이처럼 인덱스의 정렬 방향에 맞게 슬라이싱 범위를 지정하면, 내림차순으로 정렬된 데이터에서도 원하는 구간을 손쉽게 추출할 수 있습니다.