데이터 불러오기 (Data Import)

서론

이 장에서는 여러분의 데이터를 다루는 방법을 소개합니다. 여기서는 텍스트로 된 사각형(rectangular) 파일을 파이썬으로 읽어오는 방법을 배울 것입니다. 데이터 불러오기의 아주 일부분만 다루겠지만, 여기서 배우는 많은 원칙들이 다른 형태의 데이터에도 적용될 것입니다. 마지막에는 다른 유형의 데이터를 여는 몇 가지 힌트를 제공하며 마무리하겠습니다.

사전 준비

pandas 패키지가 설치되어 있어야 합니다. 세션에 pandas를 불러오려면 다음을 실행하세요.

코드 보기
import pandas as pd

이 명령이 실패하면 pandas가 설치되지 않은 것입니다. Visual Studio Code에서 터미널을 열고(Terminal -> New Terminal), 작업 중인 폴더로 이동(cd)한 후 pixi add pandas를 입력하세요. pandas가 설치되면, 스크립트 상단에 import pandas as pd를 넣어 파이썬 세션에서 pd라는 이름으로 불러오는 것이 관례입니다.

시작하기

pandas에서는 Stata(.dta), Excel(.xls, .xlsx), csv, tsv, 대용량 데이터 포맷(HDF5, parquet), JSON, SAS, SPSS, SQL 등 매우 다양한 입력 및 출력 포맷을 지원합니다. 지원되는 포맷의 전체 목록은 공식 문서에서 확인할 수 있습니다.

pandas 문서에서 인용

pandas에는 데이터를 읽고 파이썬 세션으로 불러오는 수많은 방법이 있지만, 여기서는 csv(쉼표로 구분된 값)나 tsv(탭으로 구분된 값)와 같은 평범한 텍스트 테이블 파일에 집중하겠습니다.

파일에서 데이터 읽기

텍스트 테이블 파일을 여는 데 필요한 모든 기능은 pd.read_csv()라는 단일 함수에 들어 있습니다. 이 함수는 수많은 인수를 받지만 가장 중요한 두 가지는 데이터의 경로를 지정하는 첫 번째 인수(이름 없음)와, 값들이 쉼표나 탭 등으로 구분되어 있는지 pandas에 알려주는 sep=(키워드 인수)입니다. 하지만 이 필드를 비워두면 pandas가 알아서 추측해 줍니다. 전체 인수 목록을 보려면 help(pd.read_csv)를 실행하세요.

열 이름이 있는 행(헤더 행이라고도 함)과 6행의 데이터로 구성된 간단한 CSV 파일이 터미널에서 어떻게 보이는지 확인해 보겠습니다:

코드 보기
! cat data/students.csv
Student ID,Full Name,favourite.food,mealPlan,AGE
1,Sunil Huffmann,Strawberry yoghurt,Lunch only,4
2,Barclay Lynn,French fries,Lunch only,5
3,Jayendra Lyne,N/A,Breakfast and lunch,7
4,Leon Rossini,Anchovies,Lunch only,8
5,Chidiegwu Dunkel,Pizza,Breakfast and lunch,five
6,Güvenç Attila,Ice cream,Lunch only,6

이것은 CSV 파일이므로 값들이 쉼표로 구분되어 있습니다. 이제 이를 파이썬의 pandas 데이터 프레임으로 불러와 보겠습니다:

코드 보기
students = pd.read_csv("data/students.csv")
students
Student ID Full Name favourite.food mealPlan AGE
0 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
1 2 Barclay Lynn French fries Lunch only 5
2 3 Jayendra Lyne NaN Breakfast and lunch 7
3 4 Leon Rossini Anchovies Lunch only 8
4 5 Chidiegwu Dunkel Pizza Breakfast and lunch five
5 6 Güvenç Attila Ice cream Lunch only 6

이 데이터를 다운로드하여 직접 해보고 싶다면, 이 링크로 가서 ’Raw’를 우클릭하고 “다른 이름으로 링크 저장…”을 선택하세요. ’data’라는 디렉토리에 ’students.csv’라는 이름으로 저장하세요. 이 디렉토리는 활성화된 Visual Studio Code 폴더 내에 있어야 합니다. 현재 어느 폴더에 있는지 확인하려면 다음 코드를 실행하세요.

import os

os.getcwd()  # 현재 작업 디렉토리(cwd) 가져오기

결과가 ’python4DS’라고 나온다면, 다운로드한 데이터는 ’python4DS/data/students.csv’에 있어야 합니다.

read_csv()의 첫 번째 인수는 데이터 경로였고, pandas는 이 파일이 쉼표를 구분자로 사용한다고 추측했습니다.

CSV 읽기 함수는 자동으로 새로운 인덱스(단순히 각 행의 위치)를 생성하고 데이터의 첫 번째 줄을 헤더 또는 열 이름으로 취급합니다. 하지만 이 동작을 몇 가지 방식으로 조정하고 싶을 수 있습니다.

  1. 때로는 파일 상단에 몇 줄의 메타데이터가 있을 수 있습니다. skiprows=n을 사용하여 처음 n행을 건너뛸 수 있습니다. 예: pd.read_csv("data/students.csv", skiprows=2).

  2. 데이터에 열 이름이 없을 수도 있습니다. names=에 리스트를 전달하여 read_csv()가 다른 옵션을 열 이름으로 사용하도록 할 수 있습니다. 예를 들어 pd.read_csv("data/students.csv", names=range(5))는 0부터 4까지의 숫자를 열 이름으로 넣습니다.

  3. 인덱스로 사용할 열을 변경하고 싶을 수도 있습니다. 기본 동작은 인덱스를 생성하는 것이지만, 이 데이터에서는 사용할 수 있는 ID 열이 이미 있음을 알 수 있습니다. 이를 위해 index_col= 인수를 사용합니다. 예: pd.read_csv("data/students.csv", index_col=0).

이것이 실무에서 마주치는 CSV 파일의 약 75%를 읽는 데 필요한 전부입니다. 탭으로 구분된 파일과 고정 너비 파일을 읽는 것도 동일한 함수로 수행됩니다.

첫 단계

students 데이터를 다시 살펴보겠습니다.

데이터를 읽어온 후 첫 번째 단계는 대개 분석의 나머지 과정에서 더 쉽게 다룰 수 있도록 어떤 식으로든 변환하는 작업을 포함합니다. 예를 들어, 방금 읽은 students 파일의 열 이름은 비표준 방식으로 포맷되어 있습니다.

.rename()을 사용하여 하나씩 이름을 바꾸는 것을 고려할 수도 있고, 다른 패키지의 편의 함수를 사용하여 한 번에 모두 정리하고 스네이크 케이스(snake case)로 바꿀 수도 있습니다. 여기서는 이를 위해 skimpy 패키지를 사용하겠습니다. 터미널에서 pixi add skimpy를 실행하여 설치하세요.

skimpy에서 clean_columns() 함수를 사용할 것입니다. 이 함수는 데이터 프레임을 입력받아 변수 이름을 스네이크 케이스로 변환한 데이터 프레임을 반환합니다.

코드 보기
from skimpy import clean_columns

students = clean_columns(students)
students
student_id full_name favourite_food meal_plan age
0 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
1 2 Barclay Lynn French fries Lunch only 5
2 3 Jayendra Lyne NaN Breakfast and lunch 7
3 4 Leon Rossini Anchovies Lunch only 8
4 5 Chidiegwu Dunkel Pizza Breakfast and lunch five
5 6 Güvenç Attila Ice cream Lunch only 6

데이터를 읽어온 후의 또 다른 일반적인 작업은 변수 타입을 고려하는 것입니다. favourite_food 열에는 여러 음식 항목과 NaN 값이 있는데, 이 값은 누락된 문자열이 아니라 부동 소수점 숫자로 읽혔습니다. 이 열을 명시적으로 문자열로 구성되도록 캐스팅하여 이 문제를 해결할 수 있습니다:

코드 보기
students["favourite_food"] = students["favourite_food"].astype("string")
students
student_id full_name favourite_food meal_plan age
0 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
1 2 Barclay Lynn French fries Lunch only 5
2 3 Jayendra Lyne <NA> Breakfast and lunch 7
3 4 Leon Rossini Anchovies Lunch only 8
4 5 Chidiegwu Dunkel Pizza Breakfast and lunch five
5 6 Güvenç Attila Ice cream Lunch only 6

마찬가지로 "age"에는 문자열과 정수가 섞여 있습니다! ’five’를 숫자 5로 매핑해 보겠습니다.

코드 보기
students["age"] = students["age"].replace("five", 5)
students["age"]
0    4
1    5
2    7
3    8
4    5
5    6
Name: age, dtype: object

잠시 후에 이 열도 정수 열로 바꿀 것입니다.

데이터 타입이 잘못된 또 다른 예는 meal_type입니다. 이것은 가능한 값의 집합이 알려진 범주형 변수입니다. pandas에는 이를 위한 특별한 데이터 타입이 있습니다:

코드 보기
students["meal_plan"] = students["meal_plan"].astype("category")
students["meal_plan"]
0             Lunch only
1             Lunch only
2    Breakfast and lunch
3             Lunch only
4    Breakfast and lunch
5             Lunch only
Name: meal_plan, dtype: category
Categories (2, object): ['Breakfast and lunch', 'Lunch only']

meal_type 변수의 값은 완전히 똑같지만, 변수의 타입이 object에서 category로 변경되었습니다.

타입을 적용하기 위해 열마다 한 줄씩 할당문을 쓰는 것은 좀 지루합니다. 대안으로 다음과 같이 열 이름과 타입을 매핑하는 딕셔너리를 전달할 수 있습니다:

코드 보기
students = students.astype({"student_id": "int", "full_name": "string", "age": "int"})
students.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 5 columns):
 #   Column          Non-Null Count  Dtype   
---  ------          --------------  -----   
 0   student_id      6 non-null      int64   
 1   full_name       6 non-null      string  
 2   favourite_food  5 non-null      string  
 3   meal_plan       6 non-null      category
 4   age             6 non-null      int64   
dtypes: category(1), int64(2), string(2)
memory usage: 450.0 bytes

연습 문제

  1. 필드가 “|”로 구분된 파일을 읽으려면 어떤 함수를 사용해야 할까요?

여러 파일에서 데이터 읽기

때로는 데이터가 단일 파일에 들어 있지 않고 여러 파일에 나뉘어 있을 수 있습니다. 예를 들어, 여러 달의 판매 데이터가 각 달마다 별도의 파일에 들어 있을 수 있습니다: 1월은 01-sales.csv, 2월은 02-sales.csv, 3월은 03-sales.csv와 같습니다.

pd.read_csv()를 사용하여 이 데이터들을 하나씩 읽은 다음, pd.concat() 함수를 사용하여 하나의 데이터 프레임으로 쌓아 올릴 수 있습니다. 다음과 같은 모습입니다:

코드 보기
list_of_dataframes = [
    pd.read_csv(x)
    for x in ["data/01-sales.csv", "data/02-sales.csv", "data/03-sales.csv"]
]
sales_files = pd.concat(list_of_dataframes)
sales_files
month year brand item n
0 January 2019 1 1234 3
1 January 2019 1 8721 9
2 January 2019 1 1822 2
3 January 2019 2 3333 1
4 January 2019 2 2156 9
5 January 2019 2 3987 6
6 January 2019 2 3827 6
0 February 2019 1 1234 8
1 February 2019 1 8721 2
2 February 2019 1 1822 3
3 February 2019 2 3333 1
4 February 2019 2 2156 3
5 February 2019 2 3987 6
0 March 2019 1 1234 3
1 March 2019 1 3627 1
2 March 2019 1 8820 3
3 March 2019 2 7253 1
4 March 2019 2 8766 3
5 March 2019 2 8288 6

읽어올 파일이 많다면 그 이름들을 리스트로 일일이 쓰는 것은 번거로울 수 있습니다. 대신 파이썬에 내장된 glob 패키지를 사용하여 파일 이름의 패턴을 매칭함으로써 파일을 찾을 수 있습니다. data/ 디렉토리에 다른 CSV 파일들이 있을 수 있으므로, 여기서는 sales라는 단어가 포함된 파일만 가져오기 위해 "*-sales.csv"라고 지정했습니다. 여기서 "*"는 와일드카드 역할을 하며 임의의 문자열을 나타냅니다.

코드 보기
import glob

list_of_csvs = glob.glob("data/*-sales.csv")
print("csv 리스트:")
print(list_of_csvs, "\n")
sales_files = pd.concat([pd.read_csv(x) for x in list_of_csvs])
sales_files
csv 리스트:
['data/03-sales.csv', 'data/02-sales.csv', 'data/01-sales.csv'] 
month year brand item n
0 March 2019 1 1234 3
1 March 2019 1 3627 1
2 March 2019 1 8820 3
3 March 2019 2 7253 1
4 March 2019 2 8766 3
5 March 2019 2 8288 6
0 February 2019 1 1234 8
1 February 2019 1 8721 2
2 February 2019 1 1822 3
3 February 2019 2 3333 1
4 February 2019 2 2156 3
5 February 2019 2 3987 6
0 January 2019 1 1234 3
1 January 2019 1 8721 9
2 January 2019 1 1822 2
3 January 2019 2 3333 1
4 January 2019 2 2156 9
5 January 2019 2 3987 6
6 January 2019 2 3827 6

파일로 저장하기

파일을 읽는 전형적인 패턴이 pd.read_FILETYPE()인 것과 마찬가지로, pandas 데이터 프레임을 디스크에 쓰는 모든 방법은 DATAFRAME.to_FILETYPE() 패턴을 따릅니다. 따라서 판매 데이터를 CSV 파일로 저장하려면 코드는 sales_files.to_csv(FILEPATH)가 되며, 여기서 filepath는 저장하려는 파일의 경로와 이름입니다.

데이터 타입을 설정하는 작업을 이미 훌륭하게 마친 학생 데이터를 사용하여 파일에 저장하는 예제를 살펴보겠습니다:

코드 보기
students.to_csv("data/students-clean.csv")

이제 이를 다시 읽어 들여 데이터 타입 정보를 확인해 보겠습니다:

코드 보기
pd.read_csv("data/students-clean.csv").info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 6 columns):
 #   Column          Non-Null Count  Dtype 
---  ------          --------------  ----- 
 0   Unnamed: 0      6 non-null      int64 
 1   student_id      6 non-null      int64 
 2   full_name       6 non-null      object
 3   favourite_food  5 non-null      object
 4   meal_plan       6 non-null      object
 5   age             6 non-null      int64 
dtypes: int64(3), object(3)
memory usage: 416.0+ bytes

무언가 발견하셨나요? 우리가 공들여 설정한 데이터 타입 작업의 상당 부분이 사라졌습니다! pandas가 일부 열이 정수임을 추측하긴 했지만, 문자열과 범주형 변수는 잃어버렸습니다. 그 이유는 평범한 텍스트 파일은 어떠한 문맥 정보도 가질 수 없기 때문입니다(물론 pandas가 일부 열 데이터 타입을 추측하긴 하지만요).

데이터 타입을 기억하도록 파일에 데이터를 저장하고 싶다면 다른 데이터 포맷을 사용해야 합니다. 임시 저장용으로는 매우 빠르고 다른 프로그래밍 언어와도 호환되는 feather 포맷을 권장합니다. 상호운용성(Interoperability)은 Stata의 .dta, R의 .rds, 파이썬의 .pickle과 같은 특정 언어 전용 파일 포맷을 피해야 하는 좋은 이유입니다.

feather 포맷은 pyarrow라는 패키지에 추가적인 의존성이 있습니다. 설치하려면 터미널에서 pixi add pyarrow를 실행하세요.

다음은 feather 파일로 저장하는 예제입니다:

코드 보기
students.to_feather("data/students-clean.feather")

이제 그 feather 파일을 다시 열어서 첨부된 정보를 살펴보겠습니다.

코드 보기
pd.read_feather("data/students-clean.feather").info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 5 columns):
 #   Column          Non-Null Count  Dtype   
---  ------          --------------  -----   
 0   student_id      6 non-null      int64   
 1   full_name       6 non-null      string  
 2   favourite_food  5 non-null      string  
 3   meal_plan       6 non-null      category
 4   age             6 non-null      int64   
dtypes: category(1), int64(2), string(2)
memory usage: 450.0 bytes

이 포맷으로 저장하니 데이터 타입 정보가 보존되었습니다.

다른 데이터 포맷 읽기 및 쓰기

이 장의 시작 부분에 있는 이미지를 통해 어떤 다른 포맷들이 있는지 알 수 있지만, 입출력에 관한 공식 pandas 문서에서 종합적인 목록을 찾을 수 있습니다.