이미지 로딩 중...

상품 분석 완벽 가이드 - 슬라이드 1/11
A

AI Generated

2025. 11. 15. · 3 Views

상품 분석 완벽 가이드

상품 분석은 데이터를 활용하여 고객 행동 패턴과 상품 성과를 파악하는 핵심 기술입니다. Polars를 사용하여 대량의 상품 데이터를 빠르게 분석하고, 매출 증대를 위한 인사이트를 도출하는 방법을 배워보세요.


목차

  1. Polars 데이터프레임 생성 - 상품 데이터 준비하기
  2. 컬럼 선택과 필터링 - 필요한 상품만 골라내기
  3. 그룹화와 집계 - 카테고리별 판매 현황 파악
  4. 정렬과 랭킹 - 베스트셀러 찾기
  5. 새로운 컬럼 생성 - 매출액과 수익률 계산
  6. 결측치 처리 - 불완전한 데이터 다루기
  7. 조인 연산 - 여러 데이터 소스 결합하기
  8. 피벗과 언피벗 - 데이터 구조 변환
  9. 시계열 분석 - 날짜별 판매 트렌드 파악
  10. 윈도우 함수 - 이동 평균과 누적 합계

1. Polars 데이터프레임 생성 - 상품 데이터 준비하기

시작하며

여러분이 온라인 쇼핑몰을 운영하면서 수천 개의 상품 판매 데이터를 엑셀로 관리하다가 프로그램이 자꾸 느려지거나 멈추는 경험을 해보셨나요? 데이터가 많아질수록 분석 속도가 느려지고, 원하는 인사이트를 빠르게 얻기 어려워집니다.

이런 문제는 전통적인 데이터 처리 도구의 한계 때문입니다. 특히 수만 건 이상의 데이터를 다룰 때 pandas조차도 성능 문제가 발생할 수 있습니다.

바로 이럴 때 필요한 것이 Polars입니다. Polars는 Rust로 작성된 초고속 데이터프레임 라이브러리로, 대량의 상품 데이터를 빠르게 처리하고 분석할 수 있게 해줍니다.

개요

간단히 말해서, Polars 데이터프레임은 표 형태의 데이터를 메모리에서 효율적으로 처리하는 구조입니다. 상품 분석을 시작하려면 먼저 데이터를 데이터프레임으로 불러와야 합니다.

CSV 파일, 엑셀, 데이터베이스 등 다양한 소스에서 상품 정보를 가져와 분석 가능한 형태로 만들 수 있습니다. 예를 들어, 일별 판매 데이터를 불러와서 어떤 상품이 잘 팔리는지 분석하는 경우에 매우 유용합니다.

기존에는 pandas로 데이터를 불러오고 처리했다면, 이제는 Polars로 훨씬 빠르게 동일한 작업을 수행할 수 있습니다. Polars의 핵심 특징은 첫째, 병렬 처리를 통한 초고속 성능, 둘째, 메모리 효율적인 데이터 저장, 셋째, 지연 평가(lazy evaluation)를 통한 쿼리 최적화입니다.

이러한 특징들이 대규모 상품 데이터 분석에서 빠른 의사결정을 가능하게 합니다.

코드 예제

import polars as pl

# CSV 파일에서 상품 판매 데이터 불러오기
products_df = pl.read_csv("sales_data.csv")

# 또는 딕셔너리로 직접 데이터프레임 생성
products_df = pl.DataFrame({
    "product_id": [1, 2, 3, 4, 5],
    "product_name": ["노트북", "마우스", "키보드", "모니터", "헤드셋"],
    "category": ["전자기기", "주변기기", "주변기기", "전자기기", "주변기기"],
    "price": [1200000, 35000, 89000, 450000, 120000],
    "stock": [15, 200, 150, 30, 80],
    "sales_count": [45, 320, 180, 55, 95]
})

# 데이터프레임 확인
print(products_df)

설명

이것이 하는 일: 위 코드는 상품 판매 데이터를 Polars 데이터프레임으로 생성하고, 분석 가능한 형태로 메모리에 로드합니다. 첫 번째로, pl.read_csv()는 CSV 파일에서 데이터를 읽어옵니다.

이 함수는 자동으로 컬럼 타입을 추론하고, 병렬 처리를 통해 대용량 파일도 빠르게 읽습니다. 왜 이렇게 하냐면, 실무에서는 보통 ERP나 POS 시스템에서 내보낸 CSV 파일로 데이터를 받기 때문입니다.

그 다음으로, pl.DataFrame() 생성자가 실행되면서 딕셔너리 형태의 데이터를 컬럼별로 구조화합니다. 내부적으로 Apache Arrow 포맷을 사용하여 메모리를 효율적으로 관리하며, 각 컬럼은 동일한 데이터 타입을 가진 배열로 저장됩니다.

이 과정에서 Polars는 타입 안정성을 보장하고, 후속 연산을 최적화할 수 있는 메타데이터를 생성합니다. 마지막으로, print() 함수가 데이터프레임을 출력하여 최종적으로 표 형태로 데이터를 확인할 수 있게 해줍니다.

출력 결과에는 각 컬럼의 데이터 타입과 행 개수가 함께 표시됩니다. 여러분이 이 코드를 사용하면 수만 건의 상품 데이터도 몇 초 안에 불러와서 분석을 시작할 수 있습니다.

pandas 대비 5-10배 빠른 성능, 적은 메모리 사용량, 그리고 직관적인 API를 통해 생산성을 크게 높일 수 있습니다.

실전 팁

💡 대용량 CSV 파일을 읽을 때는 pl.scan_csv()를 사용하여 지연 평가 모드로 작업하면 메모리를 절약할 수 있습니다

💡 데이터 타입이 자동으로 잘못 추론되는 경우가 있으니, dtypes 파라미터로 명시적으로 지정하는 것이 안전합니다

💡 Polars는 기본적으로 모든 CPU 코어를 사용하므로, 다른 작업과 병행할 때는 pl.Config.set_global_cpu_count()로 코어 수를 제한하세요

💡 상품 ID나 날짜 컬럼이 있다면 처음부터 인덱스로 설정하지 말고, 필요할 때 set_sorted()를 사용하여 정렬 상태를 표시하면 조인 성능이 향상됩니다

💡 데이터프레임을 생성한 후 df.schema로 컬럼 타입을 확인하는 습관을 들이면, 후속 분석에서 타입 관련 에러를 사전에 방지할 수 있습니다


2. 컬럼 선택과 필터링 - 필요한 상품만 골라내기

시작하며

여러분이 전체 상품 목록에서 특정 카테고리의 상품만 보거나, 일정 가격 이상의 프리미엄 상품만 분석하고 싶은 경우가 있나요? 수천 개의 상품 중에서 원하는 조건에 맞는 것만 찾아내는 작업은 분석의 첫 단계입니다.

이런 작업을 수작업으로 하면 시간이 오래 걸리고 실수할 가능성이 높습니다. 특히 여러 조건을 동시에 적용해야 할 때 복잡도가 기하급수적으로 증가합니다.

바로 이럴 때 필요한 것이 Polars의 컬럼 선택과 필터링 기능입니다. 간결한 문법으로 복잡한 조건을 빠르게 적용하여 원하는 데이터만 추출할 수 있습니다.

개요

간단히 말해서, 컬럼 선택은 필요한 열만 가져오는 것이고, 필터링은 특정 조건을 만족하는 행만 추출하는 것입니다. 데이터 분석에서 모든 컬럼이 항상 필요한 것은 아닙니다.

상품명, 가격, 판매량만 보고 싶다면 해당 컬럼만 선택하면 됩니다. 또한 재고가 부족한 상품이나 고가 상품만 분석하고 싶을 때 필터링을 사용합니다.

예를 들어, 100만원 이상의 전자기기 카테고리 상품만 추출하여 프리미엄 제품군의 판매 트렌드를 분석하는 경우에 매우 유용합니다. 기존에는 반복문을 돌면서 조건을 체크하고 리스트에 담았다면, 이제는 한 줄의 표현식으로 동일한 작업을 수백 배 빠르게 수행할 수 있습니다.

Polars 필터링의 핵심 특징은 첫째, 벡터화 연산으로 모든 행을 동시에 검사, 둘째, 복잡한 조건을 &(AND)와 |(OR)로 조합 가능, 셋째, 쿼리 최적화를 통한 자동 성능 향상입니다. 이러한 특징들이 대규모 데이터셋에서도 즉각적인 결과를 제공합니다.

코드 예제

import polars as pl

# 기본 데이터프레임 (이전 예제에서 이어짐)
# 특정 컬럼만 선택
selected = products_df.select(["product_name", "price", "sales_count"])

# 가격이 100,000원 이상인 상품만 필터링
expensive = products_df.filter(pl.col("price") >= 100000)

# 여러 조건 조합: 전자기기 카테고리이면서 재고가 50개 미만
critical_stock = products_df.filter(
    (pl.col("category") == "전자기기") & (pl.col("stock") < 50)
)

# 메서드 체이닝: 필터링 후 컬럼 선택
result = products_df.filter(pl.col("sales_count") > 100).select(["product_name", "sales_count"])

print(result)

설명

이것이 하는 일: 위 코드는 상품 데이터에서 필요한 컬럼만 선택하고, 다양한 비즈니스 조건에 맞는 상품만 필터링하여 분석 범위를 좁힙니다. 첫 번째로, select() 메서드는 지정된 컬럼 이름 리스트를 받아 해당 컬럼만 포함하는 새로운 데이터프레임을 생성합니다.

이는 메모리 사용량을 줄이고, 이후 연산의 성능을 향상시킵니다. 왜 이렇게 하냐면, 불필요한 컬럼을 제거함으로써 데이터 전송량과 처리 시간을 최소화할 수 있기 때문입니다.

그 다음으로, filter() 메서드가 실행되면서 pl.col()로 지정된 컬럼에 대한 조건식을 평가합니다. 내부적으로 Polars는 벡터화된 비교 연산을 수행하여 각 행이 조건을 만족하는지 불리언 배열로 생성하고, True인 행만 추출합니다.

복잡한 조건(&, |)을 사용할 때는 각 조건을 괄호로 묶어야 연산자 우선순위 문제를 피할 수 있습니다. 마지막으로, 메서드 체이닝이 실행되어 최종적으로 판매량이 100개를 넘는 상품의 이름과 판매량만 추출합니다.

Polars는 전체 파이프라인을 분석하여 불필요한 중간 결과를 생성하지 않도록 최적화합니다. 여러분이 이 코드를 사용하면 복잡한 비즈니스 요구사항을 즉시 코드로 표현할 수 있습니다.

예를 들어, "재고가 부족한 인기 상품"이나 "고가의 전자기기 중 판매 부진 제품" 같은 인사이트를 몇 초 안에 얻을 수 있으며, 이를 통해 재고 관리나 마케팅 전략을 신속하게 조정할 수 있습니다.

실전 팁

💡 필터 조건이 여러 개일 때는 각 조건을 괄호로 묶어야 합니다. (조건1) & (조건2) 형식을 반드시 지키세요

💡 문자열 비교는 대소문자를 구분하므로, pl.col("category").str.to_lowercase()로 정규화한 후 비교하면 안전합니다

💡 filter() 대신 lazy() 모드에서 작업하면 Polars가 자동으로 쿼리를 최적화하여 불필요한 컬럼 읽기를 건너뜁니다

💡 조건이 복잡할 때는 pl.col().is_in([값1, 값2])를 사용하면 여러 OR 조건을 간결하게 표현할 수 있습니다

💡 필터링 후 결과가 비어있을 수 있으니, df.height로 행 개수를 확인하는 습관을 들이면 예외 상황을 미리 대비할 수 있습니다


3. 그룹화와 집계 - 카테고리별 판매 현황 파악

시작하며

여러분이 각 상품 카테고리별로 총 매출이나 평균 가격을 계산하고 싶을 때, 일일이 수작업으로 계산하면 시간이 오래 걸리고 실수하기 쉽습니다. 특히 수백 개의 카테고리가 있다면 거의 불가능에 가깝습니다.

이런 문제는 데이터가 세분화되어 있을 때 전체적인 패턴을 파악하기 어렵기 때문에 발생합니다. 개별 상품 데이터를 보는 것만으로는 카테고리 수준의 인사이트를 얻을 수 없습니다.

바로 이럴 때 필요한 것이 그룹화(groupby)와 집계(aggregation)입니다. 동일한 속성을 가진 데이터를 묶어서 통계를 계산함으로써, 비즈니스 의사결정에 필요한 핵심 지표를 빠르게 도출할 수 있습니다.

개요

간단히 말해서, 그룹화는 같은 값을 가진 행들을 묶는 것이고, 집계는 각 그룹에 대해 합계, 평균, 개수 등의 통계를 계산하는 것입니다. 상품 분석에서 가장 흔한 작업 중 하나는 카테고리별, 브랜드별, 또는 기간별로 데이터를 그룹화하여 성과를 비교하는 것입니다.

이를 통해 어떤 카테고리가 매출에 가장 크게 기여하는지, 어떤 제품군의 재고 회전율이 낮은지 파악할 수 있습니다. 예를 들어, 월별 카테고리별 매출을 분석하여 시즌성 트렌드를 발견하는 경우에 매우 유용합니다.

기존에는 반복문으로 각 그룹을 찾아 수동으로 계산했다면, 이제는 group_by()agg() 메서드로 한 번에 모든 그룹의 통계를 계산할 수 있습니다. Polars 그룹화의 핵심 특징은 첫째, 병렬 처리를 통한 초고속 집계, 둘째, 여러 집계 함수를 동시에 적용 가능, 셋째, 다중 컬럼 그룹화 지원입니다.

이러한 특징들이 복잡한 비즈니스 분석을 간단한 코드로 구현할 수 있게 합니다.

코드 예제

import polars as pl

# 카테고리별 집계
category_stats = products_df.group_by("category").agg([
    pl.col("sales_count").sum().alias("총판매량"),
    pl.col("price").mean().alias("평균가격"),
    pl.col("product_id").count().alias("상품수"),
    pl.col("stock").min().alias("최소재고")
])

print(category_stats)

# 여러 컬럼으로 그룹화: 카테고리별 가격대별 분석
price_range = products_df.with_columns(
    pl.when(pl.col("price") >= 100000).then(pl.lit("고가"))
      .otherwise(pl.lit("저가")).alias("가격대")
).group_by(["category", "가격대"]).agg(
    pl.col("sales_count").mean().alias("평균판매량")
)

print(price_range)

설명

이것이 하는 일: 위 코드는 상품 데이터를 카테고리별로 그룹화하고, 각 그룹에 대해 판매량 합계, 가격 평균, 상품 개수 등 다양한 통계를 동시에 계산합니다. 첫 번째로, group_by("category") 메서드는 category 컬럼의 고유한 값들을 식별하고, 각 값에 해당하는 행들을 그룹으로 묶습니다.

이 과정에서 Polars는 해시 테이블을 사용하여 효율적으로 그룹을 생성하며, 모든 CPU 코어를 활용하여 병렬로 처리합니다. 왜 이렇게 하냐면, 대규모 데이터셋에서도 그룹화 작업이 병목이 되지 않도록 하기 위함입니다.

그 다음으로, agg() 메서드가 실행되면서 각 그룹에 대해 지정된 집계 함수들을 적용합니다. sum(), mean(), count(), min() 등의 함수는 각 그룹 내의 데이터를 벡터화된 방식으로 계산하며, alias()로 결과 컬럼에 의미 있는 이름을 부여합니다.

여러 집계를 리스트로 전달하면 한 번의 그룹 스캔으로 모든 통계를 계산하여 성능을 극대화합니다. 마지막으로, with_columns()과 조건문을 사용한 파생 컬럼 생성이 실행되어 최종적으로 카테고리와 가격대라는 두 차원으로 데이터를 분석합니다.

pl.when().then().otherwise() 구문은 SQL의 CASE WHEN과 유사하며, 복잡한 비즈니스 로직을 표현할 수 있습니다. 여러분이 이 코드를 사용하면 전체 상품 포트폴리오의 구조를 한눈에 파악할 수 있습니다.

어떤 카테고리가 평균 가격이 높은지, 어떤 그룹의 재고가 부족한지, 가격대별로 판매 패턴이 어떻게 다른지 즉시 알 수 있어, 구매 계획이나 가격 전략을 데이터 기반으로 수립할 수 있습니다.

실전 팁

💡 alias()로 집계 결과에 명확한 한글 이름을 부여하면 결과를 해석하기 쉽고, 나중에 코드를 읽을 때도 이해가 빠릅니다

💡 여러 집계를 수행할 때는 리스트로 한 번에 전달하면, Polars가 단일 패스로 모든 계산을 수행하여 성능이 향상됩니다

💡 그룹화 후 결과를 정렬하려면 .sort()를 체이닝하세요. 예를 들어, 매출이 높은 카테고리 순으로 보려면 .sort("총판매량", descending=True)

💡 null 값이 있는 컬럼을 그룹화하면 null도 하나의 그룹으로 취급되므로, 사전에 filter()로 제거하거나 fill_null()로 처리하세요

💡 복잡한 집계 로직은 map_groups()를 사용하여 커스텀 함수로 구현할 수 있지만, 가능하면 내장 함수를 사용하는 것이 성능상 유리합니다


4. 정렬과 랭킹 - 베스트셀러 찾기

시작하며

여러분이 판매량이 가장 많은 상위 10개 상품을 찾거나, 가격순으로 상품을 정렬하여 프리미엄 제품군을 파악하고 싶을 때가 있나요? 이런 랭킹 정보는 마케팅 전략이나 재고 관리에 매우 중요합니다.

이런 작업을 수작업으로 하면 데이터가 업데이트될 때마다 다시 해야 하고, 실수로 잘못된 순서로 정렬할 위험이 있습니다. 특히 여러 기준을 동시에 고려해야 할 때 복잡도가 높아집니다.

바로 이럴 때 필요한 것이 Polars의 정렬(sort)과 랭킹 기능입니다. 하나 이상의 컬럼을 기준으로 데이터를 빠르게 정렬하고, 각 항목의 순위를 자동으로 계산할 수 있습니다.

개요

간단히 말해서, 정렬은 데이터를 특정 컬럼의 값 순서대로 재배열하는 것이고, 랭킹은 각 행의 순위를 매기는 것입니다. 상품 분석에서 정렬은 베스트셀러 목록, 고가 상품 목록, 재고 부족 상품 우선순위 등을 만드는 데 필수적입니다.

또한 여러 기준을 조합하여 정렬할 수 있습니다. 예를 들어, 카테고리별로 먼저 그룹화한 후 각 카테고리 내에서 판매량 순으로 정렬하여 카테고리별 베스트셀러를 찾는 경우에 매우 유용합니다.

기존에는 정렬 알고리즘을 직접 구현하거나 여러 번 정렬을 반복했다면, 이제는 sort() 메서드 하나로 다중 기준 정렬을 한 번에 수행할 수 있습니다. Polars 정렬의 핵심 특징은 첫째, 병렬 정렬 알고리즘으로 대용량 데이터도 빠르게 처리, 둘째, 다중 컬럼 정렬과 각 컬럼별 오름차순/내림차순 지정 가능, 셋째, 안정 정렬(stable sort)로 동일한 값의 원래 순서 유지입니다.

이러한 특징들이 복잡한 비즈니스 요구사항을 정확하게 구현할 수 있게 합니다.

코드 예제

import polars as pl

# 판매량 기준 내림차순 정렬 (베스트셀러 순)
best_sellers = products_df.sort("sales_count", descending=True)

# 여러 컬럼으로 정렬: 카테고리 오름차순, 그 다음 가격 내림차순
multi_sort = products_df.sort(["category", "price"], descending=[False, True])

# 상위 3개 상품만 추출
top3 = products_df.sort("sales_count", descending=True).head(3)

# 카테고리별 판매량 순위 추가
with_rank = products_df.with_columns(
    pl.col("sales_count").rank(method="ordinal", descending=True)
      .over("category").alias("카테고리내순위")
).sort(["category", "카테고리내순위"])

print(with_rank)

설명

이것이 하는 일: 위 코드는 상품 데이터를 판매량, 가격, 카테고리 등 다양한 기준으로 정렬하고, 각 상품의 순위를 계산하여 비즈니스 우선순위를 파악합니다. 첫 번째로, sort() 메서드는 지정된 컬럼의 값을 비교하여 전체 데이터프레임을 재배열합니다.

descending=True를 지정하면 큰 값부터 작은 값 순으로 정렬되어 베스트셀러가 맨 위에 나타납니다. Polars는 내부적으로 병렬 정렬 알고리즘을 사용하여 수백만 행도 빠르게 정렬합니다.

왜 이렇게 하냐면, 의사결정자들은 보통 상위 N개 항목에만 관심이 있기 때문에, 정렬을 통해 중요한 정보를 먼저 볼 수 있도록 하기 위함입니다. 그 다음으로, 다중 컬럼 정렬이 실행되면서 첫 번째 컬럼(카테고리)을 먼저 정렬하고, 같은 카테고리 내에서 두 번째 컬럼(가격)으로 정렬합니다.

descending 파라미터에 리스트를 전달하여 각 컬럼마다 다른 정렬 방향을 지정할 수 있습니다. 이는 "카테고리별로 가장 비싼 상품"을 찾는 등의 복잡한 요구사항을 간단히 표현합니다.

마지막으로, rank().over() 조합이 실행되어 최종적으로 각 카테고리 내에서의 판매량 순위를 계산합니다. over("category")는 윈도우 함수로, 각 카테고리별로 독립적으로 순위를 매깁니다.

method="ordinal"은 동일한 값에 대해 먼저 나타난 항목에 더 높은 순위를 부여하는 방식입니다. 여러분이 이 코드를 사용하면 마케팅 캠페인을 위한 베스트셀러 목록을 즉시 생성하거나, 카테고리별 대표 상품을 선정하거나, 재고 보충 우선순위를 결정할 수 있습니다.

예를 들어, 각 카테고리의 상위 3개 상품을 메인 페이지에 노출하는 로직을 자동화할 수 있으며, 이를 통해 고객 경험과 매출을 동시에 개선할 수 있습니다.

실전 팁

💡 정렬 후 상위 N개만 필요하면 head(N)을 체이닝하세요. Polars는 부분 정렬 최적화를 적용하여 전체 정렬보다 빠릅니다

💡 rank() 메서드의 method 파라미터는 "ordinal", "dense", "average" 등을 지원하며, 동점 처리 방식이 비즈니스 로직에 맞게 선택하세요

💡 null 값이 있는 컬럼을 정렬하면 기본적으로 마지막에 배치되지만, nulls_last=False로 맨 앞에 올 수 있습니다

💡 대규모 데이터에서 상위 K개만 필요하면 top_k() 메서드를 사용하면 힙 정렬로 더 효율적입니다

💡 카테고리별 상위 N개를 추출하려면 group_by().head(N)보다 filter(pl.col("순위") <= N)가 더 직관적이고 유연합니다


5. 새로운 컬럼 생성 - 매출액과 수익률 계산

시작하며

여러분이 상품 분석을 하다 보면 원본 데이터에는 없지만 필요한 지표들이 있습니다. 예를 들어, 가격과 판매량은 있지만 총 매출액은 계산해야 하거나, 원가와 판매가로 수익률을 구해야 하는 경우가 많습니다.

이런 파생 지표를 매번 별도로 계산하면 코드가 복잡해지고, 데이터와 계산 로직이 분리되어 유지보수가 어려워집니다. 또한 계산 실수로 잘못된 분석 결과를 얻을 위험이 있습니다.

바로 이럴 때 필요한 것이 새로운 컬럼 생성 기능입니다. 기존 컬럼을 조합하여 비즈니스에 필요한 지표를 데이터프레임에 직접 추가함으로써, 일관되고 재사용 가능한 분석 파이프라인을 구축할 수 있습니다.

개요

간단히 말해서, 새로운 컬럼 생성은 기존 컬럼들의 값을 사용한 수식이나 조건문으로 새로운 데이터 컬럼을 만드는 것입니다. 상품 분석에서 파생 지표는 매우 중요합니다.

매출액(가격 × 판매량), 재고 회전율, 수익률, 할인율 등은 모두 원본 데이터에는 없지만 의사결정에 핵심적인 정보입니다. 이러한 지표를 미리 계산하여 컬럼으로 추가하면 이후 분석이 훨씬 간편해집니다.

예를 들어, 각 상품의 총 매출액을 계산한 후 이를 기준으로 정렬하거나 그룹화하여 매출 기여도를 분석하는 경우에 매우 유용합니다. 기존에는 별도의 변수나 딕셔너리에 계산 결과를 저장했다면, 이제는 with_columns() 메서드로 데이터프레임에 직접 추가하여 모든 데이터를 한 곳에서 관리할 수 있습니다.

새로운 컬럼 생성의 핵심 특징은 첫째, 벡터화 연산으로 모든 행에 대해 동시에 계산, 둘째, 조건문과 수식을 자유롭게 조합 가능, 셋째, 원본 데이터는 보존하고 새 컬럼만 추가입니다. 이러한 특징들이 안전하고 효율적인 데이터 변환을 가능하게 합니다.

코드 예제

import polars as pl

# 총 매출액 계산 (가격 × 판매량)
with_revenue = products_df.with_columns(
    (pl.col("price") * pl.col("sales_count")).alias("총매출액")
)

# 여러 컬럼 동시 추가
enriched = products_df.with_columns([
    (pl.col("price") * pl.col("sales_count")).alias("총매출액"),
    (pl.col("stock") / pl.col("sales_count")).alias("재고판매비율"),
    pl.when(pl.col("stock") < 50).then(pl.lit("부족"))
      .when(pl.col("stock") < 100).then(pl.lit("보통"))
      .otherwise(pl.lit("충분")).alias("재고상태")
])

# 백분율로 포맷팅
with_margin = enriched.with_columns(
    (pl.col("재고판매비율") * 100).round(2).alias("재고판매비율_퍼센트")
)

print(with_margin)

설명

이것이 하는 일: 위 코드는 가격과 판매량을 곱하여 총 매출액을 계산하고, 재고와 판매량의 비율을 구하며, 조건문으로 재고 상태를 분류하는 등 다양한 파생 지표를 생성합니다. 첫 번째로, with_columns() 메서드는 기존 데이터프레임을 변경하지 않고 새로운 컬럼이 추가된 데이터프레임을 반환합니다.

pl.col("price") * pl.col("sales_count")는 각 행의 가격과 판매량을 벡터화된 방식으로 곱하여, 모든 상품의 매출액을 동시에 계산합니다. 왜 이렇게 하냐면, 반복문을 사용하는 것보다 수백 배 빠르고, 코드도 훨씬 간결하기 때문입니다.

그 다음으로, 여러 컬럼을 리스트로 전달하면 한 번의 연산으로 모든 파생 지표가 생성됩니다. 재고판매비율은 나눗셈 연산을 통해 계산되며, pl.when().then().otherwise() 체인은 복잡한 조건 로직을 표현합니다.

이는 SQL의 CASE 문과 유사하게 작동하며, 재고 수준에 따라 "부족", "보통", "충분"으로 분류합니다. Polars는 이 모든 연산을 한 번의 데이터 스캔으로 처리하여 효율성을 극대화합니다.

마지막으로, round(2) 메서드가 실행되어 최종적으로 소수점 둘째 자리까지 반올림된 백분율 값을 생성합니다. 이렇게 만든 파생 컬럼들은 이후 필터링, 정렬, 그룹화 등 모든 분석 작업에서 일반 컬럼처럼 사용할 수 있습니다.

여러분이 이 코드를 사용하면 복잡한 비즈니스 로직을 데이터에 직접 인코딩할 수 있습니다. 예를 들어, 매출액 상위 10개 상품을 찾거나, 재고가 부족한 베스트셀러를 식별하거나, 카테고리별 평균 재고판매비율을 계산하는 등의 작업을 즉시 수행할 수 있습니다.

이를 통해 재고 최적화, 가격 전략, 프로모션 계획 등 데이터 기반 의사결정을 빠르게 할 수 있습니다.

실전 팁

💡 0으로 나누는 에러를 방지하려면 pl.when(pl.col("sales_count") == 0).then(None).otherwise(계산식)로 null 처리하세요

💡 여러 파생 컬럼을 계산할 때는 리스트로 한 번에 전달하면 Polars가 단일 패스로 처리하여 성능이 향상됩니다

💡 복잡한 수식은 가독성을 위해 변수로 분리하세요. 예: revenue_expr = pl.col("price") * pl.col("sales_count")

💡 조건문이 3개 이상이면 pl.when().then().when().then()...을 체이닝하여 여러 경우를 처리할 수 있습니다

💡 날짜 관련 파생 컬럼(요일, 분기 등)을 만들 때는 dt 네임스페이스의 메서드들을 활용하면 편리합니다


6. 결측치 처리 - 불완전한 데이터 다루기

시작하며

여러분이 실제 업무에서 받는 상품 데이터는 항상 완벽하지 않습니다. 어떤 상품은 원가 정보가 빠져 있거나, 신제품은 아직 판매 이력이 없어서 null 값이 포함되어 있는 경우가 많습니다.

이런 결측치를 그냥 방치하면 계산 오류가 발생하거나, 집계 결과가 왜곡되거나, 심지어 프로그램이 중단될 수 있습니다. 특히 평균을 계산하거나 나눗셈을 할 때 null 값은 큰 문제를 일으킵니다.

바로 이럴 때 필요한 것이 결측치 처리 기능입니다. null 값을 탐지하고, 적절한 값으로 채우거나, 해당 행을 제거함으로써 분석의 신뢰성을 확보할 수 있습니다.

개요

간단히 말해서, 결측치 처리는 데이터에서 null 값을 찾아 기본값으로 채우거나, 제거하거나, 다른 값으로 대체하는 작업입니다. 상품 분석에서 결측치는 다양한 이유로 발생합니다.

데이터 입력 누락, 시스템 오류, 신규 상품으로 아직 데이터가 없는 경우 등입니다. 이러한 결측치를 적절히 처리하지 않으면 분석 결과가 부정확해집니다.

예를 들어, 원가가 null인 상품을 0으로 채우면 수익률이 무한대로 계산될 수 있고, 그냥 제거하면 전체 상품 수가 줄어듭니다. 따라서 비즈니스 컨텍스트에 맞는 적절한 처리 방법을 선택하는 것이 중요합니다.

기존에는 반복문으로 각 값을 체크하고 if 문으로 처리했다면, 이제는 fill_null(), drop_nulls() 등의 메서드로 전체 컬럼의 결측치를 한 번에 처리할 수 있습니다. 결측치 처리의 핵심 특징은 첫째, 다양한 대체 전략(고정값, 평균, 이전값 등) 지원, 둘째, 컬럼별로 다른 전략 적용 가능, 셋째, 결측치 패턴 분석 기능입니다.

이러한 특징들이 데이터 품질을 체계적으로 관리할 수 있게 합니다.

코드 예제

import polars as pl

# 결측치가 포함된 데이터 예시
products_with_nulls = pl.DataFrame({
    "product_name": ["노트북", "마우스", "키보드", None, "헤드셋"],
    "price": [1200000, 35000, None, 450000, 120000],
    "cost": [900000, None, 60000, 350000, None],
    "sales_count": [45, 320, 180, None, 95]
})

# null 값을 0으로 채우기
filled_zero = products_with_nulls.fill_null(0)

# 컬럼별로 다른 값으로 채우기
filled_custom = products_with_nulls.with_columns([
    pl.col("price").fill_null(pl.col("price").mean()),
    pl.col("cost").fill_null(strategy="forward"),  # 이전 값으로 채우기
    pl.col("sales_count").fill_null(0)
])

# null이 있는 행 제거
cleaned = products_with_nulls.drop_nulls()

# null 개수 확인
null_counts = products_with_nulls.null_count()
print(null_counts)

설명

이것이 하는 일: 위 코드는 상품 데이터에서 null 값을 찾아 0이나 평균값으로 채우거나, 이전 행의 값으로 대체하거나, null이 있는 행을 완전히 제거합니다. 첫 번째로, fill_null(0) 메서드는 모든 컬럼의 null 값을 0으로 대체한 새로운 데이터프레임을 반환합니다.

이는 가장 단순한 방법이지만, 0이 의미 있는 값인지 고려해야 합니다. 예를 들어, 판매량이 null인 것과 0인 것은 다른 의미이므로 주의가 필요합니다.

왜 이렇게 하냐면, null 값이 있으면 수학 연산이 제대로 작동하지 않거나 예상치 못한 결과를 초래할 수 있기 때문입니다. 그 다음으로, 컬럼별 맞춤 처리가 실행되면서 각 컬럼의 특성에 맞는 전략을 적용합니다.

pl.col("price").mean()은 가격 컬럼의 평균을 계산하여 null을 채우는데, 이는 통계적으로 합리적인 대체 방법입니다. strategy="forward"는 시계열 데이터에서 유용하며, 이전 행의 값을 복사하여 null을 채웁니다.

이는 "마지막으로 알려진 값"을 사용하는 것으로, 가격이나 재고 같은 연속적인 데이터에 적합합니다. 마지막으로, drop_nulls() 메서드가 실행되어 최종적으로 하나라도 null이 있는 행을 완전히 제거합니다.

이는 데이터 손실이 발생하지만, 완전한 데이터만 분석하고 싶을 때 유용합니다. null_count()는 각 컬럼에 몇 개의 null이 있는지 보여주어, 데이터 품질을 진단하는 데 도움이 됩니다.

여러분이 이 코드를 사용하면 불완전한 데이터로 인한 분석 오류를 사전에 방지할 수 있습니다. 예를 들어, 원가 정보가 일부 누락된 상태에서 평균 수익률을 계산할 때, null을 어떻게 처리하느냐에 따라 결과가 크게 달라집니다.

비즈니스 맥락에 맞는 적절한 처리 방법을 선택함으로써 신뢰할 수 있는 인사이트를 도출할 수 있습니다.

실전 팁

💡 fill_null()을 사용하기 전에 반드시 null_count()로 각 컬럼의 결측 비율을 확인하세요. 50% 이상이면 해당 컬럼 자체를 제거하는 것이 나을 수 있습니다

💡 평균으로 채울 때는 이상치의 영향을 받으므로, median()을 사용하는 것이 더 안전할 수 있습니다

💡 drop_nulls(subset=["컬럼명"])을 사용하면 특정 컬럼의 null만 체크하여 제거할 수 있어, 불필요한 데이터 손실을 줄입니다

💡 fill_null(strategy="backward")는 다음 값으로 채우는 방법으로, 데이터가 미래에서 과거로 채워질 때 유용합니다

💡 비즈니스 로직상 null이 의미가 있다면(예: 할인 안 함 = null), "없음"이나 특수 코드로 채우는 것이 0보다 명확합니다


7. 조인 연산 - 여러 데이터 소스 결합하기

시작하며

여러분이 상품 데이터와 카테고리 상세 정보가 서로 다른 테이블에 저장되어 있거나, 판매 데이터와 고객 정보를 연결해야 하는 경우가 많습니다. 실무에서는 데이터가 여러 소스에 분산되어 있는 것이 일반적입니다.

이런 상황에서 수작업으로 데이터를 매칭하면 시간이 오래 걸리고, 실수로 잘못된 행을 연결하거나 누락시킬 위험이 큽니다. 특히 수천 건의 데이터를 매칭할 때는 거의 불가능합니다.

바로 이럴 때 필요한 것이 조인(join) 연산입니다. 공통 키를 기준으로 두 개 이상의 데이터프레임을 자동으로 결합하여, 통합된 관점에서 데이터를 분석할 수 있습니다.

개요

간단히 말해서, 조인은 두 개의 데이터프레임을 공통 컬럼(키)을 기준으로 연결하여 하나의 테이블로 만드는 것입니다. 상품 분석에서 조인은 필수적입니다.

상품 마스터 테이블과 판매 트랜잭션 테이블을 결합하거나, 카테고리 정보와 상품 정보를 합치는 등의 작업이 필요합니다. 조인을 통해 분산된 데이터를 하나로 모아 종합적인 분석이 가능해집니다.

예를 들어, 상품 ID로 상품 정보와 판매 이력을 조인하면, 어떤 상품 속성이 판매에 영향을 미치는지 분석할 수 있습니다. 기존에는 이중 반복문으로 각 행을 비교하며 매칭했다면, 이제는 join() 메서드로 한 번에 모든 매칭을 수행할 수 있습니다.

조인 연산의 핵심 특징은 첫째, 다양한 조인 타입(inner, left, outer, cross) 지원, 둘째, 해시 조인 알고리즘으로 초고속 처리, 셋째, 여러 컬럼을 키로 사용 가능입니다. 이러한 특징들이 복잡한 데이터 통합 시나리오를 효율적으로 처리할 수 있게 합니다.

코드 예제

import polars as pl

# 카테고리 상세 정보 테이블
categories = pl.DataFrame({
    "category": ["전자기기", "주변기기"],
    "category_code": ["ELEC", "ACC"],
    "margin_rate": [0.25, 0.40]  # 카테고리별 마진율
})

# 상품 테이블과 카테고리 테이블 조인
joined = products_df.join(
    categories,
    on="category",
    how="left"  # 모든 상품 유지, 매칭되는 카테고리 정보 추가
)

# 조인 후 새로운 컬럼 계산
with_margin = joined.with_columns(
    (pl.col("price") * pl.col("margin_rate")).alias("예상마진")
)

# 여러 키로 조인 (예시)
# df1.join(df2, on=["key1", "key2"], how="inner")

print(with_margin.select(["product_name", "category", "margin_rate", "예상마진"]))

설명

이것이 하는 일: 위 코드는 상품 데이터프레임과 카테고리 정보 데이터프레임을 category 컬럼을 기준으로 조인하여, 각 상품에 카테고리별 마진율 정보를 추가합니다. 첫 번째로, join() 메서드는 두 데이터프레임에서 on 파라미터로 지정된 컬럼(여기서는 "category")의 값이 일치하는 행들을 찾아 연결합니다.

how="left"는 왼쪽 데이터프레임(products_df)의 모든 행을 유지하고, 오른쪽 데이터프레임(categories)에서 매칭되는 정보를 추가하는 방식입니다. 매칭되는 카테고리가 없으면 해당 컬럼에 null이 들어갑니다.

왜 이렇게 하냐면, 상품 데이터를 손실하지 않으면서도 추가 정보를 풍부하게 만들 수 있기 때문입니다. 그 다음으로, Polars는 내부적으로 해시 조인 알고리즘을 사용하여 두 테이블을 결합합니다.

먼저 오른쪽 테이블의 키를 해시 테이블로 만들고, 왼쪽 테이블의 각 행에 대해 해시 테이블에서 매칭되는 행을 빠르게 찾습니다. 이 과정은 병렬로 실행되어 수백만 행도 몇 초 안에 처리됩니다.

조인 결과는 왼쪽 테이블의 모든 컬럼과 오른쪽 테이블의 컬럼들이 결합된 새로운 데이터프레임입니다. 마지막으로, 조인된 데이터프레임에 with_columns()를 적용하여 최종적으로 가격과 마진율을 곱한 예상 마진을 계산합니다.

이제 각 상품의 마진을 즉시 알 수 있어, 수익성 분석이 가능해집니다. 여러분이 이 코드를 사용하면 여러 시스템에서 오는 데이터를 통합하여 종합적인 분석을 할 수 있습니다.

예를 들어, ERP의 상품 정보, POS의 판매 데이터, CRM의 고객 정보를 모두 조인하여 "어떤 고객 세그먼트가 어떤 카테고리의 상품을 많이 구매하는가" 같은 복잡한 질문에 답할 수 있습니다. 이를 통해 타겟 마케팅이나 재고 배치 최적화 같은 전략적 의사결정을 데이터로 뒷받침할 수 있습니다.

실전 팁

💡 조인 전에 키 컬럼에 중복이 없는지 확인하세요. df.group_by("key").count()로 체크할 수 있습니다. 중복이 있으면 조인 결과가 폭발적으로 증가할 수 있습니다

💡 how 파라미터는 "inner"(양쪽 모두 있는 것만), "left"(왼쪽 전부), "outer"(양쪽 전부), "cross"(모든 조합) 등을 지원하며, 비즈니스 요구사항에 맞게 선택하세요

💡 조인 후 컬럼명이 겹치면 Polars가 자동으로 접미사(_right)를 추가하므로, suffix 파라미터로 명시적으로 지정하는 것이 좋습니다

💡 대용량 데이터를 조인할 때는 lazy() 모드를 사용하면 Polars가 조인 순서와 필터 위치를 최적화하여 성능을 크게 향상시킵니다

💡 조인 키의 데이터 타입이 양쪽 테이블에서 일치해야 합니다. 하나는 정수, 하나는 문자열이면 조인이 실패하므로 사전에 타입을 통일하세요


8. 피벗과 언피벗 - 데이터 구조 변환

시작하며

여러분이 월별 카테고리별 매출 데이터를 보고서로 만들 때, 행으로 나열된 데이터를 엑셀처럼 행과 열로 교차 배치하고 싶은 경우가 있습니다. 또는 반대로 넓은 테이블을 긴 형태로 변환해야 할 때도 있습니다.

이런 데이터 구조 변환을 수작업으로 하면 복잡하고 시간이 많이 걸립니다. 특히 카테고리나 기간이 많을 때는 관리가 거의 불가능해집니다.

바로 이럴 때 필요한 것이 피벗(pivot)과 언피벗(unpivot) 기능입니다. 데이터의 모양을 비즈니스 요구사항에 맞게 자유롭게 변환하여, 보고서나 시각화에 최적화된 형태로 만들 수 있습니다.

개요

간단히 말해서, 피벗은 긴 형태의 데이터를 넓은 형태로 변환하는 것이고(행을 열로), 언피벗은 그 반대로 넓은 형태를 긴 형태로 변환하는 것입니다(열을 행으로). 상품 분석에서 피벗은 요약 보고서를 만들 때 자주 사용됩니다.

예를 들어, 월별로 각 카테고리의 매출을 컬럼으로 펼쳐서 한눈에 비교하거나, 상품별로 각 지역의 판매량을 옆으로 나열하는 경우입니다. 언피벗은 반대로 여러 컬럼에 흩어진 데이터를 하나의 컬럼으로 모아 분석하기 쉽게 만듭니다.

예를 들어, 1월~12월 컬럼을 "월"과 "매출" 두 컬럼으로 변환하는 경우에 매우 유용합니다. 기존에는 복잡한 반복문과 딕셔너리 조작이 필요했다면, 이제는 pivot()unpivot() 메서드로 간단히 데이터 구조를 변환할 수 있습니다.

피벗의 핵심 특징은 첫째, 집계 함수와 함께 사용하여 요약 테이블 생성, 둘째, 다중 인덱스와 컬럼 지원, 셋째, 결측치 자동 처리입니다. 이러한 특징들이 다양한 보고 형식을 유연하게 만들 수 있게 합니다.

코드 예제

import polars as pl

# 월별 카테고리별 매출 데이터 (긴 형태)
sales_data = pl.DataFrame({
    "month": ["1월", "1월", "2월", "2월", "3월", "3월"],
    "category": ["전자기기", "주변기기", "전자기기", "주변기기", "전자기기", "주변기기"],
    "revenue": [5000000, 2000000, 5500000, 2200000, 4800000, 1900000]
})

# 피벗: 월을 행으로, 카테고리를 열로
pivoted = sales_data.pivot(
    values="revenue",
    index="month",
    columns="category"
)

print("피벗 결과:")
print(pivoted)

# 언피벗: 넓은 형태를 다시 긴 형태로
unpivoted = pivoted.unpivot(
    index="month",
    on=["전자기기", "주변기기"],
    variable_name="category",
    value_name="revenue"
)

print("\n언피벗 결과:")
print(unpivoted)

설명

이것이 하는 일: 위 코드는 월별 카테고리별 매출 데이터를 피벗하여 월을 행으로, 카테고리를 컬럼으로 배치한 크로스탭 테이블을 만들고, 다시 언피벗하여 원래의 긴 형태로 복원합니다. 첫 번째로, pivot() 메서드는 index로 지정된 컬럼(month)의 고유 값들을 행으로 만들고, columns로 지정된 컬럼(category)의 고유 값들을 새로운 컬럼명으로 만듭니다.

그리고 values로 지정된 컬럼(revenue)의 값을 각 교차점에 배치합니다. 내부적으로 Polars는 그룹화와 유사한 방식으로 데이터를 재구성하며, 동일한 행-열 조합이 여러 개 있으면 집계 함수(기본값은 first)를 적용합니다.

왜 이렇게 하냐면, 엑셀의 피벗 테이블처럼 데이터를 2차원으로 교차 배치하여 패턴을 직관적으로 파악할 수 있기 때문입니다. 그 다음으로, 피벗 결과는 month가 인덱스이고, "전자기기"와 "주변기기"가 컬럼인 넓은 형태의 테이블이 됩니다.

각 셀에는 해당 월의 해당 카테고리 매출이 들어갑니다. 이 형태는 시각화나 보고서에 바로 사용하기 적합하며, 월별로 카테고리 간 매출을 비교하기 쉽습니다.

마지막으로, unpivot() 메서드가 실행되어 최종적으로 넓은 테이블을 다시 긴 형태로 변환합니다. on 파라미터로 지정된 컬럼들("전자기기", "주변기기")이 variable_name("category")과 value_name("revenue")이라는 두 개의 새로운 컬럼으로 변환됩니다.

이는 머신러닝 모델 학습이나 시계열 분석처럼 긴 형태가 필요한 작업에 유용합니다. 여러분이 이 코드를 사용하면 데이터를 목적에 맞는 형태로 자유롭게 변환할 수 있습니다.

예를 들어, 경영진에게 보고할 때는 피벗하여 크로스탭 형태로 보여주고, 데이터 분석이나 통계 모델링을 할 때는 언피벗하여 긴 형태로 사용할 수 있습니다. 이를 통해 동일한 데이터를 다양한 관점에서 활용하여 인사이트를 극대화할 수 있습니다.

실전 팁

💡 피벗할 때 동일한 행-열 조합이 여러 개 있으면 aggregate_function 파라미터로 "sum", "mean", "count" 등을 지정하세요

💡 피벗 결과에 null이 생기는 것은 정상입니다. 특정 월에 특정 카테고리 판매가 없으면 null이 들어가므로, 필요하면 fill_null(0)로 처리하세요

💡 언피벗할 때 on 파라미터에 모든 값 컬럼을 나열하기 번거로우면, exclude 파라미터로 인덱스 컬럼만 제외할 수 있습니다

💡 대용량 데이터를 피벗하면 메모리 사용량이 크게 증가할 수 있으니, 사전에 필터링하여 필요한 데이터만 피벗하세요

💡 피벗 결과는 보통 시각화 라이브러리(matplotlib, plotly)에 바로 전달하기 좋은 형태이므로, 차트를 그리기 전 단계로 활용하세요


9. 시계열 분석 - 날짜별 판매 트렌드 파악

시작하며

여러분이 일별 또는 월별 판매 데이터를 분석하여 트렌드를 파악하거나, 특정 기간의 매출을 비교하고 싶을 때가 많습니다. 시간에 따른 변화를 이해하는 것은 비즈니스 의사결정의 핵심입니다.

이런 시계열 데이터를 문자열로 다루면 정렬이 제대로 안 되거나, 날짜 계산이 복잡하거나, 월별 집계가 어렵습니다. 특히 날짜 포맷이 다르거나 결측 날짜가 있으면 분석이 매우 까다로워집니다.

바로 이럴 때 필요한 것이 Polars의 시계열 처리 기능입니다. 날짜를 적절한 데이터 타입으로 변환하고, 날짜 연산을 수행하며, 기간별로 그룹화하여 트렌드를 분석할 수 있습니다.

개요

간단히 말해서, 시계열 분석은 날짜나 시간 데이터를 다루는 것으로, 날짜 파싱, 날짜 연산, 기간별 집계 등을 포함합니다. 상품 분석에서 시계열 데이터는 매우 중요합니다.

일별 판매량 추이, 월별 매출 성장률, 요일별 판매 패턴, 시즌별 재고 변화 등을 파악하려면 날짜를 제대로 다룰 수 있어야 합니다. 또한 특정 기간의 데이터만 추출하거나, 이동 평균을 계산하는 등의 작업이 필요합니다.

예를 들어, 최근 7일간의 평균 판매량을 계산하여 재고 보충 시점을 결정하는 경우에 매우 유용합니다. 기존에는 문자열을 수동으로 파싱하고 datetime 객체로 변환하는 번거로운 작업이 필요했다면, 이제는 Polars의 dt 네임스페이스로 간편하게 날짜를 다룰 수 있습니다.

시계열 처리의 핵심 특징은 첫째, 다양한 날짜 포맷 자동 파싱, 둘째, 날짜 컴포넌트(년, 월, 요일 등) 추출, 셋째, 날짜 연산과 기간 계산입니다. 이러한 특징들이 시간 기반 비즈니스 인사이트를 쉽게 도출할 수 있게 합니다.

코드 예제

import polars as pl
from datetime import date

# 날짜가 포함된 판매 데이터
sales_ts = pl.DataFrame({
    "date": ["2024-01-01", "2024-01-02", "2024-01-03", "2024-02-01", "2024-02-02"],
    "product": ["노트북", "마우스", "노트북", "키보드", "노트북"],
    "quantity": [5, 20, 3, 10, 7]
})

# 문자열을 날짜 타입으로 변환
sales_ts = sales_ts.with_columns(
    pl.col("date").str.strptime(pl.Date, "%Y-%m-%d")
)

# 날짜에서 월, 요일 추출
sales_ts = sales_ts.with_columns([
    pl.col("date").dt.month().alias("월"),
    pl.col("date").dt.weekday().alias("요일"),
    pl.col("date").dt.year().alias("년도")
])

# 월별 판매량 집계
monthly = sales_ts.group_by("월").agg(
    pl.col("quantity").sum().alias("월별판매량")
).sort("월")

print(monthly)

설명

이것이 하는 일: 위 코드는 문자열로 저장된 날짜를 Date 타입으로 변환하고, 날짜에서 월과 요일을 추출한 후, 월별로 판매량을 집계하여 시간에 따른 판매 패턴을 분석합니다. 첫 번째로, str.strptime() 메서드는 문자열 컬럼을 날짜 타입으로 파싱합니다.

"%Y-%m-%d" 포맷 문자열은 "2024-01-01" 같은 형식을 해석하는 방법을 지정합니다. 파싱 후 해당 컬럼은 Date 타입이 되어, 날짜 관련 연산과 정렬이 올바르게 작동합니다.

왜 이렇게 하냐면, 문자열 상태에서는 "2024-1-9"가 "2024-10-1"보다 뒤에 정렬되는 등의 문제가 발생하기 때문입니다. 그 다음으로, dt 네임스페이스의 메서드들이 실행되면서 날짜 컬럼에서 다양한 컴포넌트를 추출합니다.

dt.month()는 1~12의 월 번호를 반환하고, dt.weekday()는 0(월요일)~6(일요일)의 요일 번호를 반환합니다. 이러한 파생 컬럼은 날짜를 다양한 단위로 그룹화하거나 필터링하는 데 유용합니다.

예를 들어, 주말(요일 5, 6)의 판매 패턴을 분석하거나, 특정 월의 데이터만 추출할 수 있습니다. 마지막으로, group_by("월")agg()가 실행되어 최종적으로 각 월의 총 판매량을 계산합니다.

이를 통해 1월에 28개, 2월에 17개 판매되었다는 식으로 월별 트렌드를 한눈에 파악할 수 있습니다. sort()로 월 순서대로 정렬하면 시간의 흐름에 따른 변화를 명확히 볼 수 있습니다.

여러분이 이 코드를 사용하면 시즌성 패턴을 발견하고, 성수기와 비수기를 파악하며, 과거 데이터를 기반으로 미래 수요를 예측할 수 있습니다. 예를 들어, 특정 상품이 주말에 더 많이 팔린다는 것을 알면 주말 전에 재고를 미리 확보할 수 있고, 월별 성장률이 둔화되고 있다면 마케팅 전략을 조정할 수 있습니다.

이를 통해 데이터 기반의 선제적 경영이 가능해집니다.

실전 팁

💡 날짜 포맷이 표준이 아니면 strptime()의 포맷 문자열을 정확히 맞춰야 합니다. "%d/%m/%Y", "%Y년 %m월 %d일" 등 다양한 형식을 지원합니다

💡 dt.truncate("1mo")를 사용하면 날짜를 월 단위로 내림하여 월별 그룹화를 더 쉽게 할 수 있습니다

💡 결측 날짜를 채우려면 upsample()을 사용하여 날짜 범위를 완성하고, fill_null()로 값을 채울 수 있습니다

💡 이동 평균을 계산할 때는 rolling() 함수로 윈도우를 지정하면 트렌드를 평활화할 수 있습니다

💡 두 날짜 간의 차이를 계산하려면 pl.col("end_date") - pl.col("start_date")로 Duration 타입을 얻고, .dt.days()로 일수를 추출하세요


10. 윈도우 함수 - 이동 평균과 누적 합계

시작하며

여러분이 일별 판매량의 7일 이동 평균을 계산하여 단기 트렌드를 파악하거나, 누적 매출을 계산하여 목표 달성 진행도를 모니터링하고 싶을 때가 있습니다. 이런 계산은 각 행이 주변 행들과 관계가 있어서 단순 집계로는 불가능합니다.

이런 작업을 반복문으로 구현하면 코드가 복잡하고 느리며, 실수하기 쉽습니다. 특히 윈도우 크기를 변경하거나 여러 윈도우 함수를 동시에 적용할 때 관리가 어렵습니다.

바로 이럴 때 필요한 것이 윈도우 함수입니다. 각 행에 대해 특정 범위(윈도우)의 행들을 참조하여 계산을 수행함으로써, 이동 평균, 누적 합계, 순위 등 복잡한 분석을 간단히 구현할 수 있습니다.

개요

간단히 말해서, 윈도우 함수는 각 행을 중심으로 일정 범위의 행들을 참조하여 계산하는 함수로, 이동 평균, 누적 합계, 행간 차이 등을 계산합니다. 상품 분석에서 윈도우 함수는 시계열 트렌드 분석에 필수적입니다.

일별 판매량의 노이즈를 제거하고 전반적인 추세를 보려면 이동 평균이 유용하고, 연초부터 현재까지의 누적 매출을 추적하려면 누적 합계가 필요합니다. 또한 전일 대비 증감률을 계산하거나, 과거 N일과 비교하는 등의 작업에도 사용됩니다.

예를 들어, 30일 이동 평균을 기준으로 재고 발주량을 결정하는 경우에 매우 유용합니다. 기존에는 슬라이싱과 반복문으로 윈도우를 직접 구현했다면, 이제는 rolling(), cum_sum() 등의 메서드로 간단히 윈도우 연산을 수행할 수 있습니다.

윈도우 함수의 핵심 특징은 첫째, 다양한 윈도우 크기와 타입(고정, 가변) 지원, 둘째, 효율적인 알고리즘으로 빠른 계산, 셋째, 그룹별 독립적인 윈도우 적용 가능입니다. 이러한 특징들이 정교한 시계열 분석과 예측을 가능하게 합니다.

코드 예제

import polars as pl

# 일별 판매 데이터
daily_sales = pl.DataFrame({
    "date": pl.date_range(
        start=pl.date(2024, 1, 1),
        end=pl.date(2024, 1, 10),
        interval="1d",
        eager=True
    ),
    "sales": [100, 120, 90, 150, 140, 130, 160, 180, 170, 200]
})

# 3일 이동 평균 계산
with_rolling = daily_sales.with_columns(
    pl.col("sales").rolling_mean(window_size=3).alias("이동평균_3일")
)

# 누적 합계
with_cumsum = with_rolling.with_columns(
    pl.col("sales").cum_sum().alias("누적매출")
)

# 전일 대비 차이
with_diff = with_cumsum.with_columns(
    pl.col("sales").diff().alias("전일대비")
)

print(with_diff)

설명

이것이 하는 일: 위 코드는 일별 판매 데이터에 대해 3일 이동 평균을 계산하여 단기 트렌드를 파악하고, 누적 매출을 계산하여 총 판매량을 추적하며, 전일 대비 증감을 계산하여 변화를 모니터링합니다. 첫 번째로, rolling_mean(window_size=3) 메서드는 각 행을 중심으로 이전 2개 행과 자신을 포함한 총 3개 행의 평균을 계산합니다.

예를 들어, 3번째 행의 이동 평균은 (100 + 120 + 90) / 3 = 103.33입니다. 처음 2개 행은 충분한 이전 데이터가 없으므로 null이 됩니다.

왜 이렇게 하냐면, 일별 판매량은 요일이나 이벤트에 따라 급변하는데, 이동 평균을 사용하면 이런 노이즈를 제거하고 전반적인 상승/하락 추세를 명확히 볼 수 있기 때문입니다. 그 다음으로, cum_sum() 메서드가 실행되면서 첫 행부터 현재 행까지의 모든 판매량을 합산합니다.

첫 행은 100, 두 번째 행은 100 + 120 = 220, 세 번째 행은 220 + 90 = 310 이런 식으로 누적됩니다. 이는 연초 또는 월초부터의 누적 성과를 실시간으로 추적할 때 매우 유용하며, 목표 대비 달성률을 계산하는 데 사용됩니다.

마지막으로, diff() 메서드가 실행되어 최종적으로 현재 행과 이전 행의 차이를 계산합니다. 첫 행은 이전 행이 없으므로 null이고, 두 번째 행은 120 - 100 = 20, 세 번째 행은 90 - 120 = -30으로 전일 대비 증감을 보여줍니다.

이를 통해 판매가 급증하거나 급감한 날을 즉시 파악할 수 있습니다. 여러분이 이 코드를 사용하면 시계열 데이터의 숨겨진 패턴을 발견할 수 있습니다.

이동 평균이 상승 추세라면 전반적으로 판매가 좋아지고 있다는 신호이고, 누적 매출이 목표선에 미치지 못한다면 추가 마케팅이 필요하다는 의미입니다. 전일 대비 급증한 날을 분석하면 어떤 이벤트나 요인이 효과적이었는지 파악하여 향후 전략에 반영할 수 있습니다.

실전 팁

💡 rolling_mean()min_periods 파라미터를 설정하면 최소 몇 개의 값이 있어야 평균을 계산할지 지정할 수 있어, 초기 null을 줄일 수 있습니다

💡 이동 평균 외에도 rolling_sum(), rolling_max(), rolling_std() 등 다양한 윈도우 함수를 사용하여 다각도로 분석하세요

💡 over() 절과 윈도우 함수를 조합하면 카테고리별로 독립적인 이동 평균을 계산할 수 있습니다

💡 diff()는 기본적으로 1행 차이를 계산하지만, diff(n=7)로 일주일 전과 비교하는 등 커스터마이징 가능합니다

💡 대용량 데이터에서 윈도우 함수는 메모리를 많이 사용할 수 있으니, lazy() 모드를 사용하여 Polars가 최적화하도록 하세요


#Python#Polars#DataAnalysis#ProductAnalytics#Sales#데이터분석,Python,Polars

댓글 (0)

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