이미지 로딩 중...

DataFrame 기본 개념 완벽 가이드 - 슬라이드 1/11
A

AI Generated

2025. 11. 14. · 0 Views

DataFrame 기본 개념 완벽 가이드

데이터 분석의 핵심 도구인 DataFrame의 개념부터 실무 활용까지 완벽하게 마스터하세요. Polars를 사용한 고성능 데이터 처리 방법을 초급자도 이해하기 쉽게 설명합니다.


목차

  1. DataFrame이란 무엇인가
  2. DataFrame 구조 이해하기
  3. 열(Column) 선택과 접근
  4. 행(Row) 필터링
  5. 새로운 열 추가와 수정
  6. 데이터 정렬
  7. 그룹화와 집계
  8. 데이터 결합 (Join)
  9. 결측치 처리
  10. 데이터 타입 변환

1. DataFrame이란 무엇인가

시작하며

여러분이 엑셀 스프레드시트에서 데이터를 다루다가, 수만 개의 행을 처리해야 하는 상황을 겪어본 적 있나요? 엑셀은 느려지고, 복잡한 계산은 버거워집니다.

이런 문제는 실제 개발 현장에서 매우 자주 발생합니다. 대량의 데이터를 빠르고 효율적으로 처리하려면 프로그래밍 방식의 도구가 필요합니다.

수동 작업으로는 한계가 있고, 실수도 많이 발생하죠. 바로 이럴 때 필요한 것이 DataFrame입니다.

DataFrame은 엑셀처럼 표 형태로 데이터를 다루지만, 수백만 건의 데이터도 빠르게 처리할 수 있는 강력한 도구입니다.

개요

간단히 말해서, DataFrame은 행과 열로 구성된 2차원 테이블 형태의 데이터 구조입니다. DataFrame이 필요한 이유는 대량의 데이터를 체계적으로 관리하고 분석할 수 있기 때문입니다.

예를 들어, 전자상거래 사이트의 주문 데이터를 분석하거나, 센서에서 수집한 시계열 데이터를 처리할 때 매우 유용합니다. 각 열은 서로 다른 데이터 타입을 가질 수 있어, 숫자, 텍스트, 날짜 등을 함께 다룰 수 있습니다.

기존에는 리스트나 딕셔너리로 데이터를 관리했다면, 이제는 DataFrame으로 훨씬 직관적이고 효율적으로 작업할 수 있습니다. 데이터 필터링, 정렬, 그룹화, 집계 등의 작업이 몇 줄의 코드로 가능합니다.

DataFrame의 핵심 특징은 첫째, 각 열이 이름을 가지고 있어 데이터에 쉽게 접근할 수 있다는 점입니다. 둘째, 행과 열 단위로 데이터를 조작할 수 있어 유연합니다.

셋째, Polars 같은 라이브러리는 병렬 처리를 지원하여 대용량 데이터도 빠르게 처리합니다. 이러한 특징들이 데이터 분석 작업을 몇 배나 빠르고 쉽게 만들어줍니다.

코드 예제

import polars as pl

# CSV 파일에서 DataFrame 생성
df = pl.read_csv("sales_data.csv")

# 딕셔너리에서 DataFrame 직접 생성
df = pl.DataFrame({
    "product": ["노트북", "마우스", "키보드", "모니터"],
    "price": [1500000, 30000, 80000, 400000],
    "quantity": [5, 20, 15, 8],
    "category": ["전자기기", "주변기기", "주변기기", "전자기기"]
})

# DataFrame 확인
print(df)

설명

이것이 하는 일: DataFrame을 생성하고 데이터를 테이블 형태로 조직화하는 것입니다. 마치 데이터베이스 테이블이나 엑셀 시트처럼 구조화된 데이터를 만듭니다.

첫 번째로, pl.read_csv()는 CSV 파일에서 데이터를 읽어와 자동으로 DataFrame으로 변환합니다. 파일의 첫 행을 열 이름으로 인식하고, 각 열의 데이터 타입을 자동으로 추론합니다.

이렇게 하면 외부 데이터를 빠르게 가져올 수 있습니다. 그 다음으로, 딕셔너리를 사용한 생성 방법을 보여줍니다.

각 키가 열 이름이 되고, 값 리스트가 해당 열의 데이터가 됩니다. 이 방법은 코드 내에서 직접 데이터를 만들 때 매우 편리합니다.

Polars는 내부적으로 각 열을 효율적인 메모리 구조로 저장하여 빠른 연산을 가능하게 합니다. 마지막으로, print(df)를 실행하면 DataFrame의 내용이 보기 좋은 테이블 형태로 출력됩니다.

열 이름, 데이터 타입, 실제 값들이 정렬되어 표시되어 데이터 구조를 한눈에 파악할 수 있습니다. 여러분이 이 코드를 사용하면 CSV 파일이나 직접 만든 데이터를 즉시 분석 가능한 형태로 변환할 수 있습니다.

데이터를 로드하는 것부터 시작하는 모든 데이터 분석 프로젝트의 첫 단계가 되며, 이후 필터링, 정렬, 집계 등 다양한 분석 작업의 기반이 됩니다. Polars의 경우 pandas보다 5-10배 빠른 성능을 제공하므로, 대용량 데이터 처리에서 큰 이점을 얻을 수 있습니다.

실전 팁

💡 CSV 파일을 읽을 때는 infer_schema_length=None을 사용하면 전체 데이터를 스캔하여 더 정확한 타입 추론을 할 수 있습니다

💡 딕셔너리로 DataFrame을 만들 때는 모든 리스트의 길이가 같아야 합니다. 길이가 다르면 에러가 발생하므로 주의하세요

💡 대용량 CSV 파일은 pl.scan_csv()를 사용하면 지연 실행(lazy evaluation)으로 메모리를 절약할 수 있습니다

💡 DataFrame을 처음 만들었다면 df.head()로 처음 5행만 확인하여 데이터가 제대로 로드되었는지 검증하세요

💡 열 이름에 공백이나 특수문자가 있으면 접근이 불편하므로, 데이터 로드 직후 df.columns = ["새_이름1", "새_이름2"]로 정리하는 것이 좋습니다


2. DataFrame 구조 이해하기

시작하며

여러분이 DataFrame을 처음 만들고 나서, "이 데이터가 어떻게 생겼지? 몇 개의 행이 있지?"라는 의문이 들었던 적 있나요?

데이터의 전체 구조를 파악하지 못하면 분석을 시작할 수조차 없습니다. 이런 문제는 실제로 데이터 분석의 첫 단계에서 반드시 해결해야 합니다.

데이터의 크기, 구조, 타입을 모르면 어떤 분석이 가능한지, 어떤 전처리가 필요한지 판단할 수 없기 때문입니다. 잘못된 가정으로 분석을 시작하면 오류와 버그가 발생합니다.

바로 이럴 때 필요한 것이 DataFrame의 구조 탐색 메서드들입니다. 몇 가지 간단한 명령어로 데이터의 전체 모습을 빠르게 파악할 수 있습니다.

개요

간단히 말해서, DataFrame의 구조를 이해한다는 것은 행의 개수, 열의 개수, 각 열의 데이터 타입, 기본 통계량을 파악하는 것입니다. 이것이 필요한 이유는 데이터 분석 전에 데이터의 특성을 정확히 알아야 올바른 분석 전략을 세울 수 있기 때문입니다.

예를 들어, 고객 데이터를 분석할 때 몇 명의 고객이 있는지, 어떤 정보가 포함되어 있는지, 숫자형 데이터인지 텍스트형 데이터인지 알아야 적절한 분석 방법을 선택할 수 있습니다. 또한 결측치나 이상치가 있는지도 초기에 파악해야 합니다.

기존에는 데이터를 직접 눈으로 하나하나 확인했다면, 이제는 자동화된 메서드로 데이터 프로파일링을 즉시 수행할 수 있습니다. 수천 개의 행을 일일이 볼 필요 없이 요약 정보만으로 전체를 이해할 수 있죠.

DataFrame 구조 탐색의 핵심은 첫째, .shape로 차원을 파악하고, 둘째 .dtypes로 데이터 타입을 확인하며, 셋째 .describe()로 통계 요약을 보는 것입니다. 이 세 가지만 알아도 데이터의 80%를 이해할 수 있으며, 이후 분석 방향을 명확하게 결정할 수 있습니다.

코드 예제

import polars as pl

df = pl.DataFrame({
    "name": ["김철수", "이영희", "박민수", "정수진"],
    "age": [25, 30, 35, 28],
    "salary": [3000000, 4500000, 5500000, 4000000],
    "department": ["개발", "마케팅", "개발", "인사"]
})

# DataFrame의 크기 확인 (행, 열)
print(f"Shape: {df.shape}")

# 각 열의 데이터 타입 확인
print(f"Data types:\n{df.dtypes}")

# 기본 통계 요약
print(df.describe())

# 처음 3행 확인
print(df.head(3))

설명

이것이 하는 일: DataFrame의 메타 정보와 요약 통계를 빠르게 파악하여 데이터의 전체 그림을 그리는 것입니다. 첫 번째로, .shape 속성은 (행 개수, 열 개수) 튜플을 반환합니다.

이 예제에서는 (4, 4)가 나오는데, 4명의 직원 데이터에 4개의 속성이 있다는 뜻입니다. 이 정보로 데이터셋의 규모를 즉시 파악할 수 있으며, 반복문이나 연산의 복잡도를 예상할 수 있습니다.

수백만 행이면 다른 접근 방법이 필요하다는 것을 알 수 있죠. 그 다음으로, .dtypes는 각 열의 데이터 타입 리스트를 보여줍니다.

name과 department는 Utf8(문자열), age와 salary는 Int64(정수)로 표시됩니다. 이것이 중요한 이유는 데이터 타입에 따라 사용할 수 있는 연산이 달라지기 때문입니다.

숫자형 열에는 평균, 합계 등을 계산할 수 있지만 문자열 열에는 불가능합니다. 타입 불일치로 인한 에러를 미리 방지할 수 있습니다.

세 번째로, .describe()는 숫자형 열에 대한 통계 요약을 제공합니다. 평균(mean), 표준편차(std), 최솟값(min), 최댓값(max), 그리고 분위수(quantile)가 표시됩니다.

이를 통해 데이터의 분포와 범위를 한눈에 알 수 있습니다. 예를 들어 salary의 평균이 4,250,000원이고 표준편차가 크면 급여 편차가 크다는 것을 알 수 있습니다.

마지막으로, .head(3)는 처음 3행만 출력하여 실제 데이터가 어떻게 생겼는지 샘플을 보여줍니다. 전체 데이터를 출력하면 화면이 너무 길어지므로, 대표 샘플만 보는 것이 효율적입니다.

데이터 형식이 예상과 맞는지, 이상한 값이 없는지 빠르게 검증할 수 있습니다. 여러분이 이 코드를 사용하면 새로운 데이터셋을 받았을 때 5초 안에 데이터의 윤곽을 파악할 수 있습니다.

이는 데이터 분석의 필수 첫 단계이며, 프로젝트 기획, 전처리 계획 수립, 분석 방법 선택의 기반이 됩니다. 또한 데이터 품질 문제를 조기에 발견하여 시간을 절약할 수 있습니다.

실전 팁

💡 대용량 DataFrame의 경우 .head().tail()을 함께 사용하여 데이터의 앞뒤를 모두 확인하세요. 데이터 수집 방식에 따라 뒷부분이 다를 수 있습니다

💡 .describe()는 기본적으로 숫자형 열만 요약하지만, df.select(pl.col(pl.Utf8)).describe()로 문자열 열의 통계도 볼 수 있습니다

💡 null 값의 개수를 확인하려면 df.null_count()를 사용하세요. 결측치가 많으면 전처리가 필요합니다

💡 .schema를 사용하면 열 이름과 데이터 타입을 딕셔너리 형태로 한 번에 볼 수 있어 편리합니다

💡 Jupyter Notebook에서는 df.head()만 입력해도 자동으로 보기 좋은 테이블이 렌더링되니 적극 활용하세요


3. 열(Column) 선택과 접근

시작하며

여러분이 100개의 열이 있는 DataFrame에서 딱 3개의 열만 필요한 상황을 겪어본 적 있나요? 모든 열을 가져와서 처리하면 메모리 낭비이고 코드도 복잡해집니다.

이런 문제는 실전 데이터 분석에서 항상 발생합니다. 대부분의 경우 전체 데이터가 아니라 특정 열만 필요하기 때문입니다.

불필요한 데이터를 포함하면 처리 속도가 느려지고, 코드의 의도도 불명확해집니다. 바로 이럴 때 필요한 것이 열 선택 기능입니다.

필요한 열만 정확하게 추출하여 효율적으로 작업할 수 있습니다.

개요

간단히 말해서, 열 선택은 DataFrame에서 원하는 열만 골라내는 작업입니다. 열 선택이 필요한 이유는 데이터 분석 작업이 대부분 특정 변수에 집중되기 때문입니다.

예를 들어, 고객 데이터에서 나이와 구매액만 분석하고 싶다면 다른 개인정보 열들은 불필요합니다. 또한 대용량 데이터에서 필요한 열만 선택하면 메모리 사용량을 크게 줄일 수 있습니다.

선택된 데이터로 새로운 DataFrame을 만들어 독립적으로 작업할 수도 있습니다. 기존에는 인덱스 번호로 열에 접근했다면, 이제는 열 이름을 사용하여 직관적으로 데이터에 접근할 수 있습니다.

코드의 가독성이 높아지고 유지보수도 쉬워집니다. 열 선택의 핵심 특징은 첫째, 열 이름으로 직접 접근할 수 있다는 점입니다.

둘째, 여러 열을 동시에 선택할 수 있어 효율적입니다. 셋째, Polars의 표현식(expression) 시스템을 사용하면 복잡한 변환도 동시에 수행할 수 있습니다.

이러한 기능들이 데이터 조작을 매우 유연하고 강력하게 만들어줍니다.

코드 예제

import polars as pl

df = pl.DataFrame({
    "id": [1, 2, 3, 4],
    "name": ["김철수", "이영희", "박민수", "정수진"],
    "age": [25, 30, 35, 28],
    "salary": [3000000, 4500000, 5500000, 4000000],
    "department": ["개발", "마케팅", "개발", "인사"]
})

# 단일 열 선택 (Series 반환)
ages = df["age"]

# 여러 열 선택 (DataFrame 반환)
subset = df.select(["name", "salary"])

# 표현식을 사용한 선택과 변환
result = df.select([
    pl.col("name"),
    (pl.col("salary") / 1000000).alias("salary_millions")
])

print(result)

설명

이것이 하는 일: DataFrame에서 필요한 열만 추출하거나, 열을 선택하면서 동시에 변환하는 작업입니다. 첫 번째로, df["age"]는 단일 열을 선택하는 가장 간단한 방법입니다.

이것은 Polars의 Series 객체를 반환하는데, Series는 1차원 데이터 구조로 하나의 열을 나타냅니다. 이 방법은 빠르고 직관적이지만 단일 열에만 사용할 수 있습니다.

Series는 리스트처럼 사용할 수 있어 반복문이나 연산에 바로 활용할 수 있습니다. 그 다음으로, .select()는 여러 열을 동시에 선택할 때 사용하는 메서드입니다.

열 이름 리스트를 전달하면 해당 열들만 포함된 새로운 DataFrame이 반환됩니다. 원본 DataFrame은 변경되지 않으므로 안전합니다.

이 방법은 특히 많은 열 중에서 일부만 필요할 때 메모리를 절약하고 코드를 명확하게 만듭니다. 예제에서는 name과 salary 열만 선택했습니다.

세 번째로, 표현식을 사용한 고급 선택 방법을 보여줍니다. pl.col("salary")는 salary 열을 선택하고, 거기에 1,000,000으로 나누는 연산을 적용합니다.

.alias()는 결과 열의 이름을 지정하는데, 이 경우 "salary_millions"로 설정합니다. 이렇게 하면 선택과 변환을 한 번에 수행할 수 있어 매우 효율적입니다.

Polars의 lazy evaluation 덕분에 최적화된 실행 계획으로 처리됩니다. 여러분이 이 코드를 사용하면 대용량 데이터에서 필요한 부분만 빠르게 추출하여 분석할 수 있습니다.

메모리 사용량을 줄이고, 코드의 의도를 명확하게 표현하며, 데이터 파이프라인을 효율적으로 구성할 수 있습니다. 특히 표현식 방법은 SQL의 SELECT문과 유사하여 데이터베이스 경험이 있다면 쉽게 이해할 수 있습니다.

실전 팁

💡 여러 열을 선택할 때는 대괄호 표기법보다 .select()를 사용하는 것이 더 유연하고 표현력이 좋습니다

💡 정규식으로 열을 선택하려면 df.select(pl.col("^sale.*$"))처럼 패턴을 사용할 수 있습니다. 비슷한 이름의 여러 열을 한 번에 선택할 때 유용합니다

💡 특정 데이터 타입의 모든 열을 선택하려면 df.select(pl.col(pl.Int64))처럼 타입을 인자로 전달하세요

💡 열을 제외하고 싶다면 .select(pl.exclude(["id", "department"]))를 사용하여 역선택할 수 있습니다

💡 .get_column("name")df["name"]과 같지만 메서드 체이닝에 더 적합합니다


4. 행(Row) 필터링

시작하며

여러분이 전체 고객 데이터에서 "30세 이상이면서 구매액이 100만원 이상인 고객"만 찾아야 하는 상황을 생각해보세요. 전체 데이터를 다 보면서 수동으로 찾는 것은 불가능합니다.

이런 문제는 데이터 분석의 핵심입니다. 조건에 맞는 데이터만 추출하여 분석해야 의미 있는 인사이트를 얻을 수 있기 때문입니다.

필터링 없이는 특정 그룹에 대한 분석이 불가능하고, 전체 데이터의 평균값만 보게 되어 중요한 패턴을 놓치게 됩니다. 바로 이럴 때 필요한 것이 행 필터링 기능입니다.

복잡한 조건식으로도 원하는 데이터를 정확하게 추출할 수 있습니다.

개요

간단히 말해서, 행 필터링은 특정 조건을 만족하는 행만 선택하는 작업입니다. 필터링이 필요한 이유는 데이터 분석이 대부분 특정 조건의 데이터에 집중되기 때문입니다.

예를 들어, A/B 테스트에서 실험군만 추출하거나, 이상치를 제거하거나, 특정 기간의 데이터만 분석할 때 필터링이 필수입니다. 조건에 맞는 데이터만 보면 분석 결과가 더 정확하고 의미있어집니다.

또한 대용량 데이터에서 관심 있는 부분만 추출하면 처리 속도도 빨라집니다. 기존에는 반복문으로 하나씩 조건을 검사했다면, 이제는 벡터화된 연산으로 수백만 행을 순식간에 필터링할 수 있습니다.

코드도 훨씬 간결하고 읽기 쉬워집니다. 행 필터링의 핵심 특징은 첫째, 비교 연산자로 간단한 조건을 표현할 수 있다는 점입니다.

둘째, and(&), or(|) 연산자로 복잡한 복합 조건을 만들 수 있습니다. 셋째, Polars는 필터링을 병렬로 처리하여 대용량 데이터도 빠르게 처리합니다.

이러한 기능들이 데이터 탐색과 분석을 매우 효율적으로 만들어줍니다.

코드 예제

import polars as pl

df = pl.DataFrame({
    "name": ["김철수", "이영희", "박민수", "정수진", "최지훈"],
    "age": [25, 30, 35, 28, 42],
    "salary": [3000000, 4500000, 5500000, 4000000, 6000000],
    "department": ["개발", "마케팅", "개발", "인사", "개발"]
})

# 단일 조건 필터링
high_earners = df.filter(pl.col("salary") > 4000000)

# 복합 조건 (AND)
senior_devs = df.filter(
    (pl.col("age") >= 30) & (pl.col("department") == "개발")
)

# 복합 조건 (OR)
target_group = df.filter(
    (pl.col("salary") > 5000000) | (pl.col("age") < 30)
)

print(senior_devs)

설명

이것이 하는 일: 지정한 조건식을 모든 행에 적용하여 True인 행만 추출하는 작업입니다. 첫 번째로, 단일 조건 필터링은 pl.col("salary") > 4000000처럼 비교 연산자를 사용합니다.

이것은 salary 열의 각 값을 4,000,000과 비교하여 Boolean Series(True/False의 배열)를 만들고, True인 행만 반환합니다. Polars는 내부적으로 이 연산을 벡터화하여 처리하므로, 반복문보다 수십 배 빠릅니다.

결과는 조건을 만족하는 행들만 포함된 새로운 DataFrame입니다. 그 다음으로, 복합 조건에서 AND 연산을 보여줍니다.

& 연산자를 사용하여 두 조건을 결합하는데, 반드시 각 조건을 괄호로 묶어야 합니다. 이것은 Python의 연산자 우선순위 때문입니다.

이 예제에서는 "나이가 30세 이상이고 동시에 부서가 개발팀인" 행만 선택합니다. 두 조건을 모두 만족하는 행만 True가 되어 추출됩니다.

실무에서는 3개 이상의 조건을 결합하는 경우도 많습니다. 세 번째로, OR 연산은 | 연산자를 사용합니다.

"급여가 5,000,000 이상이거나 또는 나이가 30세 미만인" 행을 선택합니다. 둘 중 하나만 만족해도 True가 되므로 AND보다 더 많은 행이 선택됩니다.

OR 조건은 여러 그룹을 합쳐서 분석할 때 유용합니다. 여러분이 이 코드를 사용하면 복잡한 비즈니스 로직을 간결한 코드로 표현할 수 있습니다.

예를 들어 "VIP 고객 추출", "이상치 제거", "특정 세그먼트 분석" 등의 작업이 몇 줄로 가능합니다. 필터링된 데이터로 통계를 계산하거나 시각화하면 훨씬 의미 있는 결과를 얻을 수 있습니다.

Polars의 최적화된 엔진 덕분에 수백만 행도 1초 내에 필터링할 수 있습니다.

실전 팁

💡 복합 조건에서 괄호를 빼먹으면 에러가 발생합니다. &, | 연산자 사용 시 항상 각 조건을 괄호로 감싸세요

💡 NOT 연산은 ~ 기호를 사용합니다. 예: df.filter(~(pl.col("age") < 30))는 30세 이상을 선택합니다

💡 문자열 패턴 매칭은 .str.contains()를 사용하세요. 예: pl.col("name").str.contains("김")은 이름에 "김"이 포함된 행을 찾습니다

💡 여러 값 중 하나와 일치하는지 확인하려면 .is_in()을 사용하세요. 예: pl.col("department").is_in(["개발", "디자인"])

💡 null 값을 필터링하려면 .is_null() 또는 .is_not_null()을 사용합니다. 결측치 처리 전에 필수적입니다


5. 새로운 열 추가와 수정

시작하며

여러분이 기존 데이터에서 새로운 정보를 계산해야 하는 경우가 있나요? 예를 들어 "월급"에서 "연봉"을 계산하거나, "생일"에서 "나이"를 계산하는 상황입니다.

이런 문제는 데이터 분석에서 매우 흔합니다. 원본 데이터는 대부분 가공되지 않은 원시 데이터이고, 분석을 위해서는 파생 변수나 계산된 지표가 필요하기 때문입니다.

수동으로 계산해서 별도 파일로 관리하면 동기화 문제가 발생하고 오류가 생기기 쉽습니다. 바로 이럴 때 필요한 것이 열 추가와 수정 기능입니다.

기존 열을 활용하여 새로운 열을 만들거나, 기존 열의 값을 변환할 수 있습니다.

개요

간단히 말해서, 열 추가는 계산된 값이나 변환된 값을 새로운 열로 DataFrame에 추가하는 작업입니다. 이것이 필요한 이유는 분석을 위해 원본 데이터를 가공해야 하는 경우가 많기 때문입니다.

예를 들어, 전자상거래 데이터에서 "가격 × 수량"으로 총액을 계산하거나, 텍스트 데이터를 소문자로 정규화하거나, 날짜에서 요일을 추출하는 등의 작업이 필요합니다. 계산된 열을 DataFrame에 추가하면 이후 분석에서 바로 사용할 수 있어 편리합니다.

기존에는 반복문으로 각 행을 순회하며 계산했다면, 이제는 벡터화된 연산으로 전체 열에 대해 한 번에 계산할 수 있습니다. 성능도 훨씬 빠르고 코드도 간결합니다.

열 추가의 핵심 특징은 첫째, 기존 열을 조합하여 새로운 열을 만들 수 있다는 점입니다. 둘째, 원본 DataFrame을 유지하거나 변경할 수 있어 유연합니다.

셋째, Polars의 표현식 시스템으로 복잡한 변환도 간결하게 표현할 수 있습니다. 이러한 기능들이 데이터 전처리와 특성 공학(feature engineering)을 효율적으로 만들어줍니다.

코드 예제

import polars as pl

df = pl.DataFrame({
    "product": ["노트북", "마우스", "키보드"],
    "price": [1500000, 30000, 80000],
    "quantity": [5, 20, 15],
    "discount_rate": [0.1, 0.05, 0.15]
})

# 새로운 열 추가 (with_columns)
df = df.with_columns([
    # 총액 계산
    (pl.col("price") * pl.col("quantity")).alias("total"),
    # 할인 금액 계산
    (pl.col("price") * pl.col("discount_rate")).alias("discount"),
    # 최종 가격 계산
    (pl.col("price") * (1 - pl.col("discount_rate"))).alias("final_price")
])

# 기존 열 수정
df = df.with_columns(
    (pl.col("product").str.to_uppercase()).alias("product")
)

print(df)

설명

이것이 하는 일: 기존 열의 값을 사용하여 계산하고, 그 결과를 새로운 열로 DataFrame에 추가하는 작업입니다. 첫 번째로, .with_columns()는 하나 이상의 새로운 열을 추가하는 메서드입니다.

이 메서드는 원본 DataFrame을 변경하지 않고 새로운 DataFrame을 반환하므로, 재할당(df =)이 필요합니다. 표현식 리스트를 전달하면 여러 열을 동시에 추가할 수 있어 효율적입니다.

내부적으로 Polars는 이 연산들을 최적화하여 한 번의 패스로 처리합니다. 그 다음으로, 총액 계산에서 pl.col("price") * pl.col("quantity")는 두 열의 값을 요소별로 곱합니다.

첫 번째 행은 1,500,000 × 5 = 7,500,000이 되고, 이것이 "total"이라는 새 열에 저장됩니다. .alias()는 결과 열의 이름을 지정하는데, 이것이 없으면 자동으로 생성된 이름이 붙습니다.

명시적인 이름을 주는 것이 코드 가독성에 좋습니다. 세 번째로, 할인 금액과 최종 가격도 비슷한 방식으로 계산됩니다.

복잡한 수식도 표현식으로 자연스럽게 작성할 수 있습니다. 여러 열을 참조하는 계산도 가능하며, 방금 만든 열을 즉시 다른 계산에 사용할 수도 있습니다(단, 같은 with_columns 내에서는 순서에 주의).

마지막으로, 기존 열을 수정하는 예제를 보여줍니다. pl.col("product").str.to_uppercase()는 제품명을 대문자로 변환하고, 같은 이름("product")으로 alias하면 기존 열이 덮어씌워집니다.

문자열 메서드는 .str 네임스페이스에, 날짜 메서드는 .dt에 있어 체계적입니다. 여러분이 이 코드를 사용하면 복잡한 비즈니스 로직을 DataFrame에 직접 구현할 수 있습니다.

예를 들어 "고객 생애 가치 계산", "이동 평균 추가", "카테고리 인코딩" 등 다양한 특성 공학 작업이 가능합니다. 계산된 열은 이후 분석, 시각화, 머신러닝 모델링에 바로 사용할 수 있어 데이터 파이프라인을 크게 단순화합니다.

실전 팁

💡 여러 열을 추가할 때는 리스트로 한 번에 전달하세요. 여러 번 with_columns를 호출하는 것보다 효율적입니다

💡 조건부 로직은 pl.when().then().otherwise()를 사용합니다. 예: pl.when(pl.col("age") >= 30).then("senior").otherwise("junior")

💡 열 이름을 변경하려면 .alias()를 사용하거나 .rename({"old_name": "new_name"})으로 여러 열을 한 번에 변경할 수 있습니다

💡 기존 열을 삭제하려면 .drop(["column_name"])을 사용하세요. 불필요한 열을 정리하면 메모리를 절약할 수 있습니다

💡 계산 중에 에러가 발생하지 않도록 null 값을 먼저 처리하세요. .fill_null()이나 .drop_nulls()를 사용합니다


6. 데이터 정렬

시작하며

여러분이 매출 데이터를 보면서 "가장 많이 판매된 제품이 뭐지?"를 알고 싶은 적이 있나요? 무작위로 섞인 데이터에서는 패턴을 찾기 어렵습니다.

이런 문제는 데이터를 이해하는 첫 단계에서 발생합니다. 정렬되지 않은 데이터는 최대값, 최소값, 상위/하위 항목을 파악하기 어렵고, 시계열 데이터는 시간 순서대로 정렬되어야 분석이 가능합니다.

순서가 없으면 추세를 볼 수 없고 이상치도 발견하기 힘듭니다. 바로 이럴 때 필요한 것이 데이터 정렬 기능입니다.

특정 열을 기준으로 행의 순서를 재배치하여 데이터의 의미를 명확히 할 수 있습니다.

개요

간단히 말해서, 정렬은 하나 이상의 열을 기준으로 행의 순서를 오름차순이나 내림차순으로 재배치하는 작업입니다. 정렬이 필요한 이유는 데이터의 순위와 추세를 파악하기 위해서입니다.

예를 들어, 매출 데이터를 금액 기준으로 내림차순 정렬하면 베스트셀러를 즉시 확인할 수 있고, 날짜 기준으로 정렬하면 시간에 따른 변화를 추적할 수 있습니다. 정렬은 탐색적 데이터 분석(EDA)의 기본이며, 보고서나 대시보드에서 데이터를 표시할 때도 필수입니다.

기존에는 엑셀의 정렬 기능을 사용했다면, 이제는 프로그래밍 방식으로 복잡한 다중 기준 정렬도 자동화할 수 있습니다. 매번 수동으로 정렬할 필요 없이 코드로 반복 실행할 수 있습니다.

정렬의 핵심 특징은 첫째, 여러 열을 동시에 기준으로 사용할 수 있다는 점입니다. 둘째, 각 열마다 오름차순/내림차순을 독립적으로 지정할 수 있습니다.

셋째, Polars는 대용량 데이터도 빠르게 정렬할 수 있는 최적화된 알고리즘을 사용합니다. 이러한 기능들이 데이터 탐색을 매우 효과적으로 만들어줍니다.

코드 예제

import polars as pl

df = pl.DataFrame({
    "product": ["키보드", "마우스", "노트북", "모니터"],
    "category": ["주변기기", "주변기기", "전자기기", "전자기기"],
    "sales": [150, 200, 50, 80],
    "price": [80000, 30000, 1500000, 400000]
})

# 단일 열 기준 정렬 (오름차순)
sorted_by_sales = df.sort("sales")

# 내림차순 정렬
sorted_desc = df.sort("sales", descending=True)

# 다중 열 기준 정렬
sorted_multi = df.sort(
    ["category", "sales"],
    descending=[False, True]  # category는 오름차순, sales는 내림차순
)

print(sorted_multi)

설명

이것이 하는 일: 지정한 열의 값을 비교하여 전체 행의 순서를 재배치하는 작업입니다. 첫 번째로, .sort("sales")는 가장 간단한 정렬 방법으로 단일 열을 기준으로 오름차순 정렬합니다.

sales 열의 값(150, 200, 50, 80)을 비교하여 작은 값부터 큰 값 순서로 행을 재배치합니다. 결과는 50, 80, 150, 200 순서가 됩니다.

정렬은 안정 정렬(stable sort)이므로 같은 값이 여러 개 있어도 원래 순서가 유지됩니다. 그 다음으로, descending=True를 추가하면 내림차순으로 정렬됩니다.

큰 값부터 작은 값 순서가 되어 200, 150, 80, 50 순으로 배치됩니다. 이것은 Top N을 찾을 때 매우 유용합니다.

예를 들어 .sort("sales", descending=True).head(10)으로 상위 10개를 즉시 얻을 수 있습니다. 세 번째로, 다중 열 정렬은 첫 번째 기준으로 먼저 정렬하고, 값이 같으면 두 번째 기준으로 정렬합니다.

이 예제에서는 category를 먼저 오름차순으로 정렬하여 "전자기기"와 "주변기기"로 그룹화하고, 각 그룹 내에서 sales를 내림차순으로 정렬합니다. descending 파라미터에 Boolean 리스트를 전달하여 각 열의 정렬 방향을 독립적으로 제어할 수 있습니다.

이것은 "부서별 급여 순위" 같은 복잡한 요구사항을 쉽게 구현합니다. 여러분이 이 코드를 사용하면 데이터에서 인사이트를 빠르게 발견할 수 있습니다.

최고/최저 성과자를 찾거나, 시계열 데이터를 시간 순으로 정렬하거나, 카테고리별 랭킹을 만드는 등의 작업이 한 줄로 가능합니다. 정렬된 데이터는 시각화에도 더 적합하며, 사용자에게 보여줄 때도 훨씬 이해하기 쉽습니다.

실전 팁

💡 대용량 데이터를 정렬할 때는 메모리에 주의하세요. 가능하면 필요한 열만 선택한 후 정렬하는 것이 효율적입니다

💡 null 값은 기본적으로 마지막에 위치합니다. nulls_last=False로 변경할 수 있습니다

💡 정렬 후 원래 순서로 돌아가려면 미리 인덱스 열을 추가하세요. .with_row_count("idx")로 행 번호를 추가할 수 있습니다

💡 상위 N개만 필요하다면 .sort().head(N)보다 .top_k(N, by="column")이 더 빠릅니다. 부분 정렬만 수행하기 때문입니다

💡 정렬은 원본을 변경하지 않으므로 결과를 변수에 저장하거나 재할당하세요


7. 그룹화와 집계

시작하며

여러분이 부서별 평균 급여를 계산하거나, 월별 매출 합계를 구해야 하는 상황을 생각해보세요. 각 그룹마다 수동으로 필터링하고 계산하는 것은 비효율적입니다.

이런 문제는 데이터 분석의 핵심 작업입니다. 대부분의 비즈니스 질문은 "그룹별로 어떤 통계량이 어떻게 되는가?"의 형태이기 때문입니다.

개별 데이터보다는 그룹 단위의 요약 정보가 의사결정에 더 유용합니다. 바로 이럴 때 필요한 것이 그룹화와 집계 기능입니다.

데이터를 자동으로 그룹으로 나누고, 각 그룹에 대해 통계를 계산할 수 있습니다.

개요

간단히 말해서, 그룹화는 데이터를 특정 열의 값에 따라 그룹으로 나누고, 각 그룹에 대해 집계 함수를 적용하는 작업입니다. 그룹화가 필요한 이유는 범주형 변수에 따른 패턴을 발견하기 위해서입니다.

예를 들어, 제품 카테고리별 매출을 비교하거나, 지역별 고객 수를 세거나, 시간대별 트래픽을 분석할 때 그룹화가 필수입니다. 전체 평균보다는 그룹별 평균을 보면 훨씬 많은 인사이트를 얻을 수 있습니다.

그룹 간 차이를 발견하는 것이 데이터 분석의 핵심입니다. 기존에는 각 그룹을 수동으로 필터링하고 계산했다면, 이제는 한 번의 명령으로 모든 그룹에 대한 통계를 자동으로 얻을 수 있습니다.

SQL의 GROUP BY와 유사하지만 더 강력합니다. 그룹화의 핵심 특징은 첫째, 여러 열을 동시에 그룹화 기준으로 사용할 수 있다는 점입니다.

둘째, 한 번에 여러 집계 함수를 적용할 수 있습니다. 셋째, Polars는 병렬 그룹화를 지원하여 대용량 데이터도 빠르게 처리합니다.

이러한 기능들이 복잡한 분석을 간단하게 만들어줍니다.

코드 예제

import polars as pl

df = pl.DataFrame({
    "department": ["개발", "개발", "마케팅", "마케팅", "개발", "인사"],
    "employee": ["김철수", "박민수", "이영희", "정수진", "최지훈", "한미래"],
    "salary": [3000000, 5500000, 4500000, 4000000, 6000000, 3500000],
    "years": [2, 5, 3, 2, 7, 4]
})

# 단일 집계
dept_avg = df.group_by("department").agg(
    pl.col("salary").mean().alias("avg_salary")
)

# 다중 집계
dept_stats = df.group_by("department").agg([
    pl.col("salary").mean().alias("avg_salary"),
    pl.col("salary").max().alias("max_salary"),
    pl.col("employee").count().alias("employee_count"),
    pl.col("years").sum().alias("total_years")
])

print(dept_stats)

설명

이것이 하는 일: 데이터를 그룹으로 나누고, 각 그룹에 대해 통계 함수를 적용하여 요약 정보를 생성하는 작업입니다. 첫 번째로, .group_by("department")는 department 열의 고유한 값들("개발", "마케팅", "인사")을 찾아 각각의 그룹을 만듭니다.

내부적으로 해시 테이블을 사용하여 같은 값을 가진 행들을 빠르게 그룹화합니다. 이 시점에서는 아직 계산이 수행되지 않고, 그룹화 계획만 준비됩니다.

Polars의 lazy evaluation 덕분에 최적화된 실행 계획이 만들어집니다. 그 다음으로, .agg()는 각 그룹에 집계 함수를 적용합니다.

pl.col("salary").mean()은 각 그룹의 salary 열에 대해 평균을 계산합니다. "개발" 그룹의 급여들(3,000,000, 5,500,000, 6,000,000)의 평균이 계산되고, "마케팅", "인사" 그룹도 각각 계산됩니다.

결과는 각 부서당 한 행씩 포함하는 새로운 DataFrame이 됩니다. .alias()로 결과 열의 이름을 명확히 지정하는 것이 중요합니다.

세 번째로, 다중 집계는 리스트로 여러 표현식을 전달합니다. 평균, 최댓값, 개수, 합계를 동시에 계산할 수 있습니다.

각 집계는 독립적으로 수행되며, 결과 DataFrame에 여러 열로 나타납니다. .count()는 각 그룹의 행 개수를 세고, .sum()은 합계를 계산합니다.

한 번의 그룹화로 필요한 모든 통계를 얻을 수 있어 매우 효율적입니다. 여러분이 이 코드를 사용하면 복잡한 비즈니스 질문에 즉시 답할 수 있습니다.

"어느 부서의 급여가 가장 높은가?", "각 카테고리의 매출 비중은?", "월별 성장률은?" 같은 질문들이 몇 줄의 코드로 해결됩니다. 그룹화된 결과는 바로 차트로 시각화하거나 보고서에 사용할 수 있어, 데이터 분석 워크플로우의 핵심입니다.

실전 팁

💡 여러 열로 그룹화하려면 .group_by(["department", "team"])처럼 리스트를 전달하세요. 계층적 분석이 가능합니다

💡 사용 가능한 집계 함수: mean(), sum(), count(), min(), max(), std(), median(), first(), last() 등이 있습니다

💡 조건부 집계는 filter()와 결합하세요. 예: pl.col("salary").filter(pl.col("years") > 3).mean()으로 경력 3년 이상의 평균만 계산할 수 있습니다

💡 그룹 내 순위는 .over()를 사용합니다. 예: pl.col("salary").rank().over("department")로 부서 내 급여 순위를 매길 수 있습니다

💡 그룹화 결과를 정렬하려면 .group_by().agg().sort()로 체이닝하세요. 상위 그룹을 찾을 때 유용합니다


8. 데이터 결합 (Join)

시작하며

여러분이 고객 정보 테이블과 주문 정보 테이블이 따로 있는 상황을 겪어본 적 있나요? 각 주문에 대한 고객 이름을 알려면 두 테이블을 연결해야 합니다.

이런 문제는 실무에서 항상 발생합니다. 데이터는 대부분 정규화되어 여러 테이블에 나뉘어 저장되기 때문입니다.

관련된 정보를 하나로 합쳐야 완전한 분석이 가능합니다. 수동으로 매칭하는 것은 불가능하고 오류도 많이 발생합니다.

바로 이럴 때 필요한 것이 데이터 결합(Join) 기능입니다. 공통 키를 기준으로 두 DataFrame을 자동으로 연결할 수 있습니다.

개요

간단히 말해서, Join은 두 DataFrame을 공통 열(키)을 기준으로 연결하여 하나의 DataFrame으로 합치는 작업입니다. Join이 필요한 이유는 관련 데이터가 여러 소스에 분산되어 있기 때문입니다.

예를 들어, 고객 데이터베이스와 거래 데이터베이스가 별도로 있을 때, 고객 정보와 거래 정보를 함께 분석하려면 Join이 필수입니다. 데이터베이스의 정규화 원칙 때문에 이런 구조가 일반적이며, 분석을 위해서는 역정규화(denormalization)가 필요합니다.

기존에는 VLOOKUP이나 반복문으로 매칭했다면, 이제는 데이터베이스처럼 선언적으로 Join을 수행할 수 있습니다. SQL의 JOIN과 동일한 개념이지만 DataFrame에서 사용할 수 있습니다.

Join의 핵심 특징은 첫째, Inner, Left, Right, Outer 등 다양한 Join 타입을 지원한다는 점입니다. 둘째, 여러 열을 동시에 키로 사용할 수 있습니다.

셋째, Polars는 해시 조인을 사용하여 대용량 데이터도 빠르게 결합합니다. 이러한 기능들이 복잡한 데이터 통합을 간단하게 만들어줍니다.

코드 예제

import polars as pl

# 고객 정보
customers = pl.DataFrame({
    "customer_id": [1, 2, 3, 4],
    "name": ["김철수", "이영희", "박민수", "정수진"],
    "city": ["서울", "부산", "서울", "대구"]
})

# 주문 정보
orders = pl.DataFrame({
    "order_id": [101, 102, 103, 104, 105],
    "customer_id": [1, 2, 1, 3, 5],
    "amount": [50000, 30000, 70000, 40000, 60000]
})

# Inner Join (양쪽에 모두 있는 데이터만)
inner = customers.join(orders, on="customer_id", how="inner")

# Left Join (왼쪽 DataFrame의 모든 데이터 유지)
left = customers.join(orders, on="customer_id", how="left")

print(left)

설명

이것이 하는 일: 두 DataFrame에서 공통 열의 값이 일치하는 행들을 찾아 하나의 행으로 결합하는 작업입니다. 첫 번째로, .join() 메서드는 첫 번째 인자로 결합할 DataFrame을, on 파라미터로 키 열 이름을 받습니다.

Polars는 내부적으로 customer_id를 기준으로 해시 테이블을 만들어 빠르게 매칭합니다. 이 예제에서는 customers DataFrame의 각 행을 orders DataFrame에서 같은 customer_id를 가진 행들과 매칭합니다.

한 고객이 여러 주문을 한 경우 고객 정보가 여러 번 반복됩니다. 그 다음으로, how="inner"는 양쪽 DataFrame에 모두 존재하는 customer_id만 결과에 포함합니다.

customer_id가 5인 주문은 customers에 없으므로 제외되고, customer_id가 4인 고객은 주문이 없으므로 제외됩니다. Inner Join은 완전히 매칭되는 데이터만 필요할 때 사용하며, 데이터 무결성을 보장합니다.

결과 행의 개수는 원본보다 적거나 많을 수 있습니다. 세 번째로, how="left"는 왼쪽 DataFrame(customers)의 모든 행을 유지하고, 매칭되는 오른쪽 DataFrame(orders)의 데이터를 추가합니다.

정수진(customer_id=4)은 주문이 없지만 결과에 포함되며, order_id와 amount 열은 null로 채워집니다. Left Join은 기준 테이블의 모든 데이터를 유지하면서 추가 정보를 보강할 때 사용합니다.

"모든 고객의 주문 현황"을 보려면 Left Join이 적합합니다. 네 번째로, Join 후의 DataFrame은 양쪽의 모든 열을 포함합니다.

만약 같은 이름의 열이 있다면 자동으로 접미사(_right)가 붙습니다. 이를 방지하려면 suffix를 명시적으로 지정할 수 있습니다.

결과는 완전히 새로운 DataFrame이며 원본은 변경되지 않습니다. 여러분이 이 코드를 사용하면 여러 소스의 데이터를 통합하여 종합적인 분석이 가능합니다.

"고객별 총 구매액", "지역별 평균 주문금액", "고객 세그먼트별 구매 패턴" 같은 복잡한 분석이 Join을 통해 가능해집니다. 데이터 웨어하우스나 BI 시스템의 핵심 기능이며, 실무에서 가장 자주 사용되는 기능 중 하나입니다.

실전 팁

💡 Join 타입 선택 가이드: 양쪽 모두 필요하면 inner, 기준 테이블 유지는 left, 모든 데이터 보존은 outer를 사용하세요

💡 여러 열로 Join하려면 on=["col1", "col2"]처럼 리스트를 전달하세요. 복합 키 매칭이 가능합니다

💡 열 이름이 다르면 left_on="customer_id", right_on="client_id"처럼 각각 지정할 수 있습니다

💡 Join 전에 키 열의 중복을 확인하세요. df["customer_id"].is_duplicated().sum()으로 체크할 수 있습니다

💡 대용량 데이터 Join은 메모리를 많이 사용합니다. pl.scan_csv()로 lazy loading을 사용하면 메모리를 절약할 수 있습니다


9. 결측치 처리

시작하며

여러분이 데이터를 분석하다가 비어있는 값, 즉 null이나 NaN을 발견한 적 있나요? 이런 값들은 계산을 방해하고 에러를 발생시킵니다.

이런 문제는 실제 데이터에서 피할 수 없습니다. 센서 고장, 설문 미응답, 데이터 입력 누락 등 다양한 이유로 결측치가 발생하기 때문입니다.

결측치를 제대로 처리하지 않으면 분석 결과가 왜곡되거나 프로그램이 중단됩니다. 바로 이럴 때 필요한 것이 결측치 처리 기능입니다.

결측치를 탐지하고, 제거하거나, 적절한 값으로 채울 수 있습니다.

개요

간단히 말해서, 결측치 처리는 null 값을 찾아서 제거하거나 다른 값으로 대체하는 작업입니다. 결측치 처리가 필요한 이유는 대부분의 통계 함수와 머신러닝 알고리즘이 null 값을 처리하지 못하기 때문입니다.

예를 들어, 평균을 계산할 때 null이 포함되면 결과가 null이 되거나 에러가 발생합니다. 결측치의 비율과 패턴을 분석하면 데이터 품질 문제를 발견할 수도 있습니다.

적절한 처리 전략을 선택하면 데이터 손실을 최소화하면서 분석을 진행할 수 있습니다. 기존에는 수동으로 null을 찾아 처리했다면, 이제는 자동화된 메서드로 일관되게 처리할 수 있습니다.

다양한 대체 전략(평균, 중앙값, 최빈값, 전진/후진 채우기 등)을 선택할 수 있습니다. 결측치 처리의 핵심 특징은 첫째, null 값의 위치와 개수를 쉽게 파악할 수 있다는 점입니다.

둘째, 다양한 대체 전략을 지원하여 데이터 특성에 맞게 선택할 수 있습니다. 셋째, 열별로 다른 전략을 적용할 수 있어 유연합니다.

이러한 기능들이 데이터 전처리를 효과적으로 만들어줍니다.

코드 예제

import polars as pl

df = pl.DataFrame({
    "name": ["김철수", "이영희", None, "정수진"],
    "age": [25, None, 35, 28],
    "salary": [3000000, 4500000, None, 4000000],
    "department": ["개발", "마케팅", "개발", None]
})

# 결측치 확인
null_counts = df.null_count()
print("Null counts:", null_counts)

# 결측치가 있는 행 제거
cleaned = df.drop_nulls()

# 특정 값으로 채우기
filled = df.fill_null(0)  # 모든 null을 0으로

# 열별로 다른 값으로 채우기
filled_custom = df.with_columns([
    pl.col("age").fill_null(pl.col("age").mean()),
    pl.col("salary").fill_null(pl.col("salary").median()),
    pl.col("name").fill_null("미상"),
    pl.col("department").fill_null("미지정")
])

print(filled_custom)

설명

이것이 하는 일: 데이터에서 누락된 값을 찾아내고, 분석에 적합한 형태로 변환하는 작업입니다. 첫 번째로, .null_count()는 각 열의 null 값 개수를 세어 보여줍니다.

이것은 데이터 품질 진단의 첫 단계로, 어느 열에 얼마나 많은 결측치가 있는지 파악합니다. 결측치가 50% 이상이면 해당 열을 삭제하는 것을 고려해야 하고, 10% 미만이면 대체나 삭제 전략이 모두 가능합니다.

결측 패턴을 분석하면 데이터 수집 과정의 문제를 발견할 수도 있습니다. 그 다음으로, .drop_nulls()는 null 값이 하나라도 있는 행을 완전히 제거합니다.

이것은 가장 간단한 방법이지만 데이터 손실이 클 수 있습니다. 예제에서는 모든 행이 최소 하나의 null을 가지고 있어 결과가 빈 DataFrame이 될 수 있습니다.

특정 열만 기준으로 삭제하려면 .drop_nulls(subset=["age"])처럼 사용할 수 있습니다. 이 방법은 결측치가 적고 무작위로 발생한 경우에 적합합니다.

세 번째로, .fill_null(0)은 모든 null 값을 지정한 값(여기서는 0)으로 대체합니다. 단순하지만 모든 열에 같은 값을 사용하면 데이터 의미가 왜곡될 수 있습니다.

숫자형 데이터에는 0이나 평균값이 적합하지만, 문자열에는 "미상" 같은 플레이스홀더가 필요합니다. 네 번째로, 열별 맞춤형 대체는 .with_columns().fill_null()을 결합하여 구현합니다.

age는 평균값으로, salary는 중앙값으로 채워 이상치의 영향을 줄입니다. name과 department는 의미 있는 문자열로 채웁니다.

pl.col("age").mean()은 현재 age 열의 평균을 계산하여 null을 대체하는데, 이것은 통계적으로 합리적인 추정값입니다. 각 열의 특성에 맞는 전략을 선택하는 것이 중요합니다.

여러분이 이 코드를 사용하면 불완전한 데이터에서도 의미 있는 분석을 수행할 수 있습니다. 결측치를 적절히 처리하면 데이터 손실을 최소화하면서 통계 계산과 모델링을 진행할 수 있습니다.

실무에서는 결측치 처리 전략에 따라 분석 결과가 달라질 수 있으므로, 여러 방법을 시도하고 비교하는 것이 좋습니다.

실전 팁

💡 결측치 패턴을 시각화하면 무작위 결측(MCAR)인지 구조적 결측(MNAR)인지 판단할 수 있습니다

💡 시계열 데이터는 .fill_null(strategy="forward")로 이전 값으로 채우거나 strategy="backward"로 다음 값으로 채울 수 있습니다

💡 머신러닝 모델링 전에는 결측치를 반드시 처리해야 합니다. 대부분의 알고리즘은 null을 허용하지 않습니다

💡 결측치가 특정 조건과 연관되어 있다면 단순 대체보다는 예측 모델로 대체하는 것이 더 정확할 수 있습니다

💡 .is_null().is_not_null()로 결측치 여부를 Boolean 열로 만들어 분석에 활용할 수 있습니다


10. 데이터 타입 변환

시작하며

여러분이 CSV 파일을 읽었는데 숫자 열이 문자열로 인식되어 계산이 안 되는 상황을 겪어본 적 있나요? 데이터 타입이 맞지 않으면 원하는 연산을 수행할 수 없습니다.

이런 문제는 데이터를 다룰 때 자주 발생합니다. 외부에서 가져온 데이터는 타입 정보가 없거나 잘못 추론되는 경우가 많기 때문입니다.

문자열로 저장된 날짜, 숫자로 인식된 범주형 변수 등 타입 불일치는 분석을 방해합니다. 바로 이럴 때 필요한 것이 데이터 타입 변환 기능입니다.

열의 데이터 타입을 올바르게 변경하여 적절한 연산을 수행할 수 있습니다.

개요

간단히 말해서, 데이터 타입 변환은 열의 데이터를 다른 타입으로 변경하는 작업입니다. 타입 변환이 필요한 이유는 각 데이터 타입마다 사용할 수 있는 연산이 다르기 때문입니다.

예를 들어, 문자열로 저장된 "100"과 "200"을 더하면 "100200"이 되지만, 숫자로 변환하면 300이 됩니다. 날짜 타입으로 변환하면 날짜 연산(차이 계산, 월 추출 등)이 가능해집니다.

올바른 타입을 사용하면 메모리 효율도 좋아지고 연산 속도도 빨라집니다. 기존에는 수동으로 값을 파싱하고 변환했다면, 이제는 자동화된 캐스팅 함수로 전체 열을 한 번에 변환할 수 있습니다.

에러 처리도 내장되어 있어 안전합니다. 타입 변환의 핵심 특징은 첫째, 다양한 타입 간 변환을 지원한다는 점입니다(숫자, 문자열, 날짜, Boolean 등).

둘째, 변환 실패 시 null로 처리하거나 에러를 발생시키는 등 옵션을 선택할 수 있습니다. 셋째, Polars는 타입이 명확하여 성능 최적화가 가능합니다.

이러한 기능들이 데이터 전처리를 효율적으로 만들어줍니다.

코드 예제

import polars as pl

df = pl.DataFrame({
    "id": ["1", "2", "3", "4"],
    "price": ["1500", "3000", "2500", "4000"],
    "date": ["2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04"],
    "is_active": ["true", "false", "true", "true"]
})

print("Original types:", df.dtypes)

# 문자열을 숫자로 변환
df = df.with_columns([
    pl.col("id").cast(pl.Int32),
    pl.col("price").cast(pl.Float64)
])

# 문자열을 날짜로 변환
df = df.with_columns(
    pl.col("date").str.to_date()
)

# 문자열을 Boolean으로 변환
df = df.with_columns(
    (pl.col("is_active") == "true").alias("is_active")
)

print("Converted types:", df.dtypes)
print(df)

설명

이것이 하는 일: 열의 각 값을 지정한 데이터 타입으로 변환하여 타입에 맞는 연산과 저장 방식을 적용하는 작업입니다. 첫 번째로, .cast()는 명시적 타입 변환을 수행하는 메서드입니다.

pl.col("id").cast(pl.Int32)는 "1", "2", "3", "4" 문자열을 정수 1, 2, 3, 4로 변환합니다. 내부적으로 각 문자열을 파싱하여 숫자로 변환하는데, 변환할 수 없는 값이 있으면 기본적으로 에러가 발생합니다.

strict=False를 추가하면 변환 실패 시 null로 처리할 수 있습니다. 타입이 변경되면 메모리 사용량과 연산 방식이 완전히 달라집니다.

그 다음으로, price를 Float64로 변환하는 이유는 소수점 계산이 필요할 수 있기 때문입니다. 정수보다 범위가 넓고 소수를 표현할 수 있습니다.

숫자 타입으로 변환하면 산술 연산(+, -, *, /)과 통계 함수(평균, 합계 등)를 사용할 수 있습니다. 문자열일 때는 불가능했던 연산들입니다.

세 번째로, 날짜 변환은 .str.to_date()를 사용합니다. 이것은 문자열 네임스페이스(.str)의 메서드로, ISO 형식("YYYY-MM-DD")의 날짜 문자열을 Date 타입으로 변환합니다.

다른 형식은 format="%d/%m/%Y" 파라미터로 지정할 수 있습니다. Date 타입으로 변환하면 .dt 네임스페이스의 날짜 메서드(년도 추출, 요일 계산, 날짜 차이 등)를 사용할 수 있습니다.

네 번째로, Boolean 변환은 비교 연산을 사용합니다. pl.col("is_active") == "true"는 각 값이 "true"와 같은지 비교하여 True/False Boolean 값을 생성합니다.

직접 cast(pl.Boolean)을 사용할 수도 있지만, 문자열의 경우 명시적 비교가 더 명확합니다. Boolean 타입은 메모리를 적게 사용하고 논리 연산에 최적화되어 있습니다.

여러분이 이 코드를 사용하면 데이터를 분석에 적합한 형태로 준비할 수 있습니다. 올바른 타입을 사용하면 타입 에러를 방지하고, 적절한 연산을 수행하며, 메모리와 성능을 최적화할 수 있습니다.

데이터 파이프라인의 초기 단계에서 타입을 확인하고 수정하는 것이 이후 모든 작업을 수월하게 만듭니다.

실전 팁

💡 타입 변환 전에 .dtypes로 현재 타입을 확인하고, 변환 후 다시 확인하여 의도대로 변환되었는지 검증하세요

💡 대량의 변환 실패가 예상되면 strict=False를 사용하여 null로 처리하고, 나중에 null_count()로 확인하세요

💡 날짜/시간 데이터는 to_datetime()으로 시간 정보까지 포함한 Datetime 타입으로 변환할 수 있습니다

💡 범주형 데이터는 cast(pl.Categorical)로 변환하면 메모리를 크게 절약하고 그룹화 연산이 빨라집니다

💡 타입 변환은 비용이 큰 연산이므로, 데이터 로드 시 pl.read_csv(dtypes={...})로 올바른 타입을 지정하는 것이 더 효율적입니다


#Python#DataFrame#Polars#DataAnalysis#DataStructure#데이터분석,Python,Polars

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.