목록으로

프로그래밍 · Python

Python 완전정복 시리즈 #24: pandas로 데이터 분석하고 정리하기

BeanCon
Python pandas로 표 형태 데이터를 분석하고 정리하는 방법을 설명하는 대표 이미지

Python 완전정복 시리즈 24편입니다. pandas로 표 형태 데이터를 다루는 방법을 정리했습니다. Series와 DataFrame, 열과 행 선택, 조건 필터링, 정렬, 새 열 추가, Copy-on-Write, 결측치와 중복 처리, 문자열과 날짜 정리, 그룹 집계, 피벗 테이블, CSV와 Excel 입출력, 대용량 데이터 처리와 미니 프로젝트까지 다룹니다.

목차

메타 설명
수천 개의 데이터를 반복문으로 하나씩 처리하고 있나요? Python의 대표적인 데이터 분석 라이브러리 pandas를 이용하면 CSV와 Excel 파일을 불러오고, 필요한 데이터만 검색하고, 정렬·집계·결측치 처리까지 간단하게 수행할 수 있습니다.

지난 시간에는 Selenium과 Playwright를 이용해 JavaScript 웹사이트를 자동으로 조작하고 데이터를 수집하는 방법을 배웠습니다.

웹 크롤링에 성공한 우리는 수천 개의 상품명, 가격, 뉴스 제목, 작성일을 확보했습니다.

그런데 데이터를 모으고 나니 새로운 문제가 생겼습니다.

상품 A, 15000, 서울, 판매완료
상품 B, 32000, 부산, 판매중
상품 C, 가격 없음, 서울, 판매중
상품 A, 15000, 서울, 판매완료
상품 D, 8900, 대전, 판매중
...

데이터가 너무 많습니다.

중복된 데이터도 있고, 가격이 없는 데이터도 있으며, 날짜 형식도 제각각입니다.

Python 리스트와 반복문으로 하나씩 확인하려니 머릿속에서 작은 경고등이 켜집니다.

“이 데이터를 언제 다 정리하지?” 😵‍💫

이때 데이터 정리 특공대가 등장합니다.

바로 pandas입니다.

pandas를 사용하면 다음 작업을 표를 다루듯 수행할 수 있습니다.

* CSV와 Excel 파일 불러오기* 행과 열 선택하기* 조건에 맞는 데이터 검색하기* 데이터 정렬하기* 빈 값 처리하기* 중복 데이터 제거하기* 그룹별 합계와 평균 계산하기* 여러 데이터 합치기* 분석 결과를 새로운 파일로 저장하기

수천 줄의 데이터도 pandas 앞에서는 제법 얌전한 표가 됩니다.

오늘은 데이터 창고의 불을 켜고, 여기저기 쌓여 있는 숫자 상자를 차곡차곡 정리해 보겠습니다. 📦🐼

1. pandas란 무엇인가?

pandas는 Python에서 표 형태의 데이터를 분석하고 처리하기 위한 라이브러리입니다.

다음과 같이 행과 열로 구성된 데이터를 다루는 데 특히 유용합니다.

상품명카테고리가격판매량
무선 키보드전자기기3500012
텀블러생활용품1800030
노트북 거치대전자기기420008

우리가 일상에서 접하는 많은 데이터가 이런 표 형태로 존재합니다.

* Excel 문서* CSV 파일* 데이터베이스 조회 결과* 웹 크롤링 결과* 공공데이터* 서버 로그* 센서 측정값* 쇼핑몰 주문 내역* 설문 조사 결과* 주식과 환율 데이터

pandas는 이러한 데이터를 저장하는 핵심 자료구조로 `Series`와 `DataFrame`을 제공합니다. 공식 문서에서도 두 자료구조를 pandas의 기본 데이터 구조로 설명하고 있습니다.

pandas를 불러올 때는 일반적으로 다음과 같이 작성합니다.

import pandas as pd

왜 `pandas`를 그대로 쓰지 않고 `pd`라고 줄일까요?

pandas 코드에서는 `pd.DataFrame()`, `pd.read_csv()`, `pd.to_datetime()`처럼 라이브러리 이름을 자주 사용하기 때문입니다.

import pandas as pd

이 표현은 거의 pandas 세계의 공식 별명처럼 사용됩니다.

2. pandas가 필요한 이유

Python 기본 문법만으로도 데이터를 분석할 수 있습니다.

예를 들어 학생들의 평균 점수를 계산해 보겠습니다.

students = [
    {"name": "민준", "score": 85},
    {"name": "서연", "score": 92},
    {"name": "지훈", "score": 78},
]

total = 0

for student in students:
    total += student["score"]

average = total / len(students)

print(average)

결과는 다음과 같습니다.

85.0

학생이 세 명일 때는 큰 문제가 없습니다.

하지만 데이터가 10만 개이고 다음 조건까지 추가된다면 어떨까요?

* 지역이 서울인 데이터만 선택* 결측치 제외* 부서별 평균 계산* 평균이 높은 순서로 정렬* 결과를 CSV로 저장

반복문만으로 구현할 수는 있지만 코드가 점점 복잡해집니다.

pandas를 사용하면 다음처럼 작성할 수 있습니다.

seoul_data = df[df["지역"] == "서울"]

result = (
    seoul_data
    .dropna(subset=["점수"])
    .groupby("부서")["점수"]
    .mean()
    .sort_values(ascending=False)
)

코드가 마치 데이터 처리 과정을 그대로 읽어 주는 것 같습니다.

서울 데이터 선택
→ 점수가 없는 행 제거
→ 부서별로 그룹화
→ 평균 계산
→ 높은 순서로 정렬

pandas는 데이터를 하나씩 꺼내 처리하기보다, 열이나 행 단위의 연산을 표현할 수 있도록 도와줍니다.

반복문이 데이터를 한 명씩 면담하는 방식이라면, pandas는 강당에 모두 모아 한 번에 안내 방송을 하는 방식입니다. 📢

3. pandas 설치하기

터미널이나 명령 프롬프트에서 다음 명령어를 실행합니다.

pip install pandas

pandas 공식 문서에서는 PyPI를 이용한 `pip install pandas`와 conda-forge를 이용한 설치 방법을 안내하고 있습니다. 또한 프로젝트별 패키지 충돌을 줄이기 위해 가상환경에서 실행하는 방식을 권장합니다.

Windows 가상환경 설치

python -m venv venv
venv\Scripts\activate
pip install pandas

macOS 또는 Linux 가상환경 설치

python3 -m venv venv
source venv/bin/activate
pip install pandas

설치 확인하기

import pandas as pd

print(pd.__version__)

오류 없이 버전이 출력되면 설치가 완료된 것입니다.

Excel 기능까지 함께 설치하기

Excel 파일을 읽고 저장하려면 추가 라이브러리가 필요할 수 있습니다.

공식 설치 문서에서는 Excel 관련 선택적 의존성을 다음과 같이 한꺼번에 설치할 수 있도록 안내합니다.

pip install "pandas[excel]"

간단하게 `openpyxl`만 설치할 수도 있습니다.

pip install openpyxl

4. Series와 DataFrame 이해하기

pandas를 이해하려면 두 가지 핵심 자료구조를 알아야 합니다.

Series
DataFrame

이름은 조금 낯설지만 구조는 어렵지 않습니다.

Series란?

Series는 한 줄의 열 데이터와 비슷합니다.

import pandas as pd

scores = pd.Series([85, 92, 78, 88])

print(scores)

실행 결과는 다음과 같습니다.

0    85
1    92
2    78
3    88
dtype: int64

왼쪽의 `0`, `1`, `2`, `3`은 인덱스입니다.

오른쪽의 `85`, `92`, `78`, `88`은 실제 값입니다.

인덱스    값
0        85
1        92
2        78
3        88

직접 인덱스를 지정할 수도 있습니다.

scores = pd.Series(
    [85, 92, 78, 88],
    index=["민준", "서연", "지훈", "수빈"]
)

print(scores)

결과는 다음과 같습니다.

민준    85
서연    92
지훈    78
수빈    88
dtype: int64

특정 학생의 점수는 다음과 같이 확인합니다.

print(scores["서연"])
92

Series는 정수 위치와 라벨을 이용한 데이터 접근을 모두 지원하며, 각 값은 인덱스를 통해 식별됩니다.

DataFrame이란?

DataFrame은 여러 개의 Series가 모인 2차원 표라고 이해할 수 있습니다.

import pandas as pd

data = {
    "이름": ["민준", "서연", "지훈", "수빈"],
    "점수": [85, 92, 78, 88],
    "지역": ["서울", "부산", "서울", "대전"]
}

df = pd.DataFrame(data)

print(df)

결과는 다음과 같습니다.

이름  점수  지역
0  민준  85  서울
1  서연  92  부산
2  지훈  78  서울
3  수빈  88  대전

구조를 그림처럼 표현하면 다음과 같습니다.

열              열             열
       "이름"          "점수"          "지역"
          ↓              ↓              ↓
0       민준             85             서울
1       서연             92             부산
2       지훈             78             서울
3       수빈             88             대전
↑
행 인덱스

DataFrame은 Excel의 워크시트나 데이터베이스 테이블과 비슷한 모습입니다.

앞으로 대부분의 pandas 작업은 DataFrame을 중심으로 진행합니다.

5. DataFrame 직접 만들기

딕셔너리로 만들기

가장 자주 사용하는 방식입니다.

import pandas as pd

data = {
    "상품명": [
        "무선 키보드",
        "텀블러",
        "노트북 거치대",
        "마우스"
    ],
    "카테고리": [
        "전자기기",
        "생활용품",
        "전자기기",
        "전자기기"
    ],
    "가격": [
        35000,
        18000,
        42000,
        25000
    ],
    "판매량": [
        12,
        30,
        8,
        20
    ]
}

df = pd.DataFrame(data)

print(df)

리스트와 딕셔너리로 만들기

웹 크롤링 결과가 다음과 같은 형태로 저장되는 경우가 많습니다.

products = [
    {
        "상품명": "무선 키보드",
        "가격": 35000,
        "판매량": 12
    },
    {
        "상품명": "텀블러",
        "가격": 18000,
        "판매량": 30
    },
    {
        "상품명": "노트북 거치대",
        "가격": 42000,
        "판매량": 8
    }
]

df = pd.DataFrame(products)

print(df)

지난 시간의 Selenium이나 Playwright 크롤링 결과가 리스트 안의 딕셔너리 형태라면, 그대로 DataFrame으로 변환할 수 있습니다.

df = pd.DataFrame(crawling_results)

크롤링으로 잡아 온 야생 데이터가 한 줄의 코드로 표 안에 착석합니다. 🪑

6. 데이터의 전체 구조 확인하기

실제 분석에서는 데이터를 받자마자 계산부터 시작하면 안 됩니다.

먼저 데이터의 상태를 살펴봐야 합니다.

데이터 분석가에게 `head()`, `info()`, `describe()`는 사건 현장에 도착한 탐정의 손전등과 같습니다. 🔦

예제 데이터를 준비하겠습니다.

import pandas as pd

data = {
    "상품명": [
        "무선 키보드",
        "텀블러",
        "노트북 거치대",
        "마우스",
        "USB 허브",
        "손목 받침대"
    ],
    "카테고리": [
        "전자기기",
        "생활용품",
        "전자기기",
        "전자기기",
        "전자기기",
        "생활용품"
    ],
    "가격": [
        35000,
        18000,
        42000,
        25000,
        29000,
        15000
    ],
    "판매량": [
        12,
        30,
        8,
        20,
        16,
        25
    ]
}

df = pd.DataFrame(data)

앞부분 확인하기

print(df.head())

기본적으로 처음 5개 행을 보여 줍니다.

print(df.head(3))

처음 3개 행만 확인합니다.

뒷부분 확인하기

print(df.tail())

마지막 5개 행을 보여 줍니다.

print(df.tail(2))

마지막 2개 행만 확인합니다.

행과 열 개수 확인하기

print(df.shape)

결과:

(6, 4)

의미는 다음과 같습니다.

6개의 행
4개의 열

열 이름 확인하기

print(df.columns)

결과:

Index(['상품명', '카테고리', '가격', '판매량'], dtype='str')

리스트로 변환할 수도 있습니다.

print(df.columns.tolist())

데이터 타입 확인하기

print(df.dtypes)

결과 예시:

상품명      str
카테고리    str
가격       int64
판매량      int64
dtype: object

전체 정보 확인하기

df.info()

`info()`를 사용하면 다음 정보를 확인할 수 있습니다.

* 행 개수* 열 이름* 결측치가 아닌 값의 개수* 각 열의 데이터 타입* 메모리 사용 정보

숫자 데이터 요약하기

print(df.describe())

일반적으로 다음 통계가 출력됩니다.

* `count`: 데이터 개수* `mean`: 평균* `std`: 표준편차* `min`: 최솟값* `25%`: 1사분위수* `50%`: 중앙값* `75%`: 3사분위수* `max`: 최댓값

print(df["가격"].mean())
print(df["가격"].min())
print(df["가격"].max())

pandas의 통계 연산은 일반적으로 결측치를 제외하고 계산하며, 공식 입문 문서에서도 `mean()`, `median()`, `min()`, `max()` 등의 요약 통계 기능을 소개합니다.

7. 열 선택하기

하나의 열 선택하기

print(df["상품명"])

결과는 Series입니다.

0       무선 키보드
1          텀블러
2    노트북 거치대
3          마우스
4        USB 허브
5       손목 받침대
Name: 상품명, dtype: str

데이터 타입도 확인해 보겠습니다.

product_names = df["상품명"]

print(type(product_names))
<class 'pandas.Series'>

여러 열 선택하기

여러 열을 선택할 때는 열 이름을 리스트로 전달합니다.

print(df[["상품명", "가격"]])

주의할 점은 대괄호가 두 번 사용된다는 것입니다.

df[["상품명", "가격"]]

첫 번째 대괄호는 DataFrame 선택 연산입니다.

두 번째 대괄호는 열 이름을 담은 리스트입니다.

df[
    ["상품명", "가격"]
]

결과는 DataFrame입니다.

selected = df[["상품명", "가격"]]

print(type(selected))
<class 'pandas.DataFrame'>

정리하면 다음과 같습니다.

df["가격"]
Series 반환
df[["가격"]]
DataFrame 반환

대괄호 하나 차이지만 결과의 차원은 달라집니다.

pandas에서 대괄호는 장식이 아니라 데이터의 세계관을 결정하는 문입니다.

8. 행 선택하기

pandas에서는 행을 선택할 때 `loc`와 `iloc`를 자주 사용합니다.

공식 문서에서는 `loc`를 라벨 기반 선택 도구로, `iloc`를 정수 위치 기반 선택 도구로 설명합니다.

loc: 인덱스 이름으로 선택

print(df.loc[0])

인덱스가 `0`인 행을 선택합니다.

여러 행을 선택할 수도 있습니다.

print(df.loc[0:2])

`loc`의 범위 선택은 마지막 인덱스를 포함합니다.

따라서 `0:2`는 다음 행을 반환합니다.

0번 행
1번 행
2번 행

loc로 행과 열 함께 선택하기

print(
    df.loc[
        0:2,
        ["상품명", "가격"]
    ]
)

구조는 다음과 같습니다.

df.loc[행, 열]

iloc: 정수 위치로 선택

print(df.iloc[0])

첫 번째 행을 선택합니다.

print(df.iloc[0:3])

첫 번째 행부터 세 번째 행 직전까지 선택합니다.

Python의 일반적인 슬라이싱처럼 마지막 위치는 포함하지 않습니다.

iloc[0:3]
→ 0번째, 1번째, 2번째 행

iloc로 행과 열 선택하기

print(df.iloc[0:3, 0:2])

의미는 다음과 같습니다.

0번째부터 2번째까지의 행
0번째부터 1번째까지의 열

loc와 iloc 비교

구분기준예시
`loc`인덱스와 열의 이름`df.loc[0, "상품명"]`
`iloc`행과 열의 위치 번호`df.iloc[0, 0]`

기억법은 간단합니다.

loc
→ label을 떠올리기

iloc
→ integer location을 떠올리기

9. 조건에 맞는 데이터 필터링하기

pandas의 강력한 기능 중 하나는 조건 검색입니다.

예를 들어 가격이 30,000원 이상인 상품만 선택해 보겠습니다.

expensive = df[df["가격"] >= 30000]

print(expensive)

먼저 다음 표현의 결과를 확인해 보겠습니다.

print(df["가격"] >= 30000)

결과:

0     True
1    False
2     True
3    False
4    False
5    False
Name: 가격, dtype: bool

각 행이 조건을 만족하는지 `True`와 `False`로 표시됩니다.

이 결과를 다시 DataFrame에 전달하면 `True`인 행만 선택됩니다.

df[df["가격"] >= 30000]

문자열 조건 검색

카테고리가 전자기기인 상품을 선택합니다.

electronics = df[
    df["카테고리"] == "전자기기"
]

print(electronics)

여러 조건 동시에 사용하기

전자기기이면서 가격이 30,000원 이상인 상품을 찾겠습니다.

result = df[
    (df["카테고리"] == "전자기기")
    & (df["가격"] >= 30000)
]

print(result)

pandas에서 여러 조건을 연결할 때는 다음 연산자를 사용합니다.

& : 그리고
| : 또는
~ : 반대

각 조건은 괄호로 감싸는 것이 안전합니다.

(
    df["카테고리"] == "전자기기"
)

또는 조건 사용하기

가격이 20,000원 미만이거나 판매량이 20개 이상인 상품을 찾습니다.

result = df[
    (df["가격"] < 20000)
    | (df["판매량"] >= 20)
]

print(result)

특정 값 목록에 포함되는지 확인하기

categories = [
    "전자기기",
    "도서"
]

result = df[
    df["카테고리"].isin(categories)
]

print(result)

특정 범위 선택하기

가격이 20,000원부터 40,000원 사이인 상품을 찾습니다.

result = df[
    df["가격"].between(
        20000,
        40000
    )
]

print(result)

특정 문자열 포함 여부 확인하기

상품명에 `무선`이 포함된 상품을 찾습니다.

result = df[
    df["상품명"].str.contains(
        "무선",
        na=False
    )
]

print(result)

공식 pandas 문서에서도 조건식과 불리언 배열을 이용해 DataFrame의 일부를 선택하는 방식을 제공하고 있습니다.

10. 데이터 정렬하기

특정 열을 기준으로 오름차순 정렬

result = df.sort_values(
    by="가격"
)

print(result)

가격이 낮은 상품부터 정렬됩니다.

내림차순 정렬

result = df.sort_values(
    by="가격",
    ascending=False
)

print(result)

가격이 높은 상품부터 정렬됩니다.

여러 기준으로 정렬

카테고리를 먼저 정렬하고, 같은 카테고리 안에서는 판매량이 높은 순서로 정렬하겠습니다.

result = df.sort_values(
    by=[
        "카테고리",
        "판매량"
    ],
    ascending=[
        True,
        False
    ]
)

print(result)

의미는 다음과 같습니다.

카테고리
→ 오름차순

판매량
→ 내림차순

원본 DataFrame 변경하기

기본적으로 `sort_values()`는 정렬된 새로운 DataFrame을 반환합니다.

sorted_df = df.sort_values(
    by="가격"
)

원본에 다시 저장하려면 다음처럼 작성합니다.

df = df.sort_values(
    by="가격"
)

또는 `inplace=True`를 사용할 수도 있습니다.

df.sort_values(
    by="가격",
    inplace=True
)

초보 단계에서는 작업 결과를 변수에 다시 저장하는 방식이 흐름을 이해하기 쉽습니다.

11. 새로운 열 추가하기

기존 열을 이용해 새로운 열을 만들 수 있습니다.

매출 열 추가하기

df["매출"] = (
    df["가격"]
    * df["판매량"]
)

print(df)

각 상품의 가격과 판매량이 자동으로 곱해집니다.

가격 × 판매량 = 매출

Python 반복문을 사용한다면 각 행을 하나씩 순회해야 하지만, pandas에서는 열 전체에 연산을 적용할 수 있습니다.

고정된 값 추가하기

df["통화"] = "KRW"

모든 행에 `KRW`가 입력됩니다.

조건에 따라 값 추가하기

df["인기상품"] = (
    df["판매량"] >= 20
)

print(df)

결과는 `True` 또는 `False`입니다.

조금 더 읽기 쉬운 값으로 바꿔 보겠습니다.

df["판매등급"] = [
    "인기" if sales >= 20 else "일반"
    for sales in df["판매량"]
]

print(df)

apply()로 함수 적용하기

def get_grade(sales):
    if sales >= 25:
        return "최우수"
    elif sales >= 15:
        return "우수"
    else:
        return "일반"


df["판매등급"] = (
    df["판매량"]
    .apply(get_grade)
)

print(df)

`apply()`는 각 값에 함수를 적용할 수 있어 편리합니다.

다만 단순한 사칙연산이나 비교 연산은 가능한 한 Series 전체 연산으로 표현하는 편이 간결하고 빠른 경우가 많습니다.

12. 값 수정하기와 Copy-on-Write

특정 조건에 맞는 값을 수정할 때는 `loc`를 사용하는 것이 좋습니다.

판매량이 10개 미만인 상품의 판매등급을 `관심필요`로 바꿔 보겠습니다.

df.loc[
    df["판매량"] < 10,
    "판매등급"
] = "관심필요"

구조는 다음과 같습니다.

df.loc[
    수정할 행 조건,
    수정할 열
] = 새로운 값

피해야 할 연속 선택 방식

다음과 같은 코드는 피해야 합니다.

df[df["판매량"] < 10]["판매등급"] = "관심필요"

이처럼 선택 결과에 다시 선택 연산을 연결하는 방식을 연속 할당 또는 Chained Assignment라고 합니다.

pandas 3.0에서는 Copy-on-Write가 기본이자 유일한 동작 방식이며, 연속 할당으로 원본 DataFrame을 변경할 수 없습니다. 공식 문서에서는 원본을 수정할 때 `loc`를 사용하도록 안내합니다.

따라서 다음 패턴을 기억해야 합니다.

df.loc[
    조건,
    "열이름"
] = 값

특정 한 칸 수정하기

df.loc[0, "가격"] = 34000

또는 `at`을 사용할 수 있습니다.

df.at[0, "가격"] = 34000

위치 번호만 알고 있다면 `iat`을 사용할 수 있습니다.

df.iat[0, 2] = 34000

정리하면 다음과 같습니다.

도구기준용도
`loc`라벨여러 행과 열 선택
`iloc`위치 번호여러 행과 열 선택
`at`라벨하나의 값 선택
`iat`위치 번호하나의 값 선택

13. 열과 행 삭제하기

열 삭제하기

result = df.drop(
    columns=["통화"]
)

print(result)

여러 열도 삭제할 수 있습니다.

result = df.drop(
    columns=[
        "통화",
        "인기상품"
    ]
)

원본에 반영하려면 다시 저장합니다.

df = df.drop(
    columns=["통화"]
)

del 사용하기

del df["인기상품"]

행 삭제하기

인덱스가 0인 행을 삭제합니다.

result = df.drop(
    index=0
)

print(result)

여러 행을 삭제할 수도 있습니다.

result = df.drop(
    index=[0, 2]
)

조건에 맞는 행 제외하기

가격이 20,000원 이상인 데이터만 다시 저장하면, 20,000원 미만인 행은 제외됩니다.

df = df[
    df["가격"] >= 20000
]

인덱스 다시 정리하기

행을 삭제한 뒤 인덱스가 다음처럼 남을 수 있습니다.

0
2
3
5

인덱스를 다시 `0`부터 정리하려면 다음과 같이 작성합니다.

df = df.reset_index(
    drop=True
)

`drop=True`를 사용하지 않으면 기존 인덱스가 새로운 열로 추가됩니다.

14. 결측치 확인하고 처리하기

결측치는 값이 없거나 알 수 없는 상태를 의미합니다.

예를 들어 가격이나 판매량이 비어 있는 데이터입니다.

import pandas as pd

data = {
    "상품명": [
        "키보드",
        "마우스",
        "모니터",
        "USB 허브"
    ],
    "가격": [
        35000,
        None,
        250000,
        29000
    ],
    "판매량": [
        12,
        20,
        None,
        16
    ]
}

df = pd.DataFrame(data)

print(df)

결과 예시:

상품명        가격   판매량
0    키보드   35000.0   12.0
1    마우스       NaN   20.0
2    모니터  250000.0    NaN
3  USB 허브   29000.0   16.0

pandas는 데이터 타입에 따라 `NaN`, `NA`, `NaT` 등으로 결측값을 표현할 수 있습니다. `NaT`는 날짜와 시간 데이터에서 사용되는 결측값입니다.

결측치 확인하기

print(df.isna())

결측치 위치는 `True`로 표시됩니다.

열별 결측치 개수 확인하기

print(df.isna().sum())

결과:

상품명    0
가격     1
판매량    1
dtype: int64

분석을 시작하기 전에 다음 코드를 습관처럼 실행하면 좋습니다.

print(df.isna().sum())

데이터 구석에 숨어 있던 빈칸들이 하나씩 손을 듭니다. 🙋

결측치를 특정 값으로 채우기

df["가격"] = (
    df["가격"]
    .fillna(0)
)

판매량의 결측치를 평균으로 채워 보겠습니다.

average_sales = (
    df["판매량"]
    .mean()
)

df["판매량"] = (
    df["판매량"]
    .fillna(average_sales)
)

앞의 값으로 채우기

df["가격"] = (
    df["가격"]
    .ffill()
)

뒤의 값으로 채우기

df["가격"] = (
    df["가격"]
    .bfill()
)

결측치가 있는 행 삭제하기

result = df.dropna()

하나라도 결측치가 있는 행이 제거됩니다.

특정 열이 비어 있을 때만 삭제하기

result = df.dropna(
    subset=["가격"]
)

결측치를 무조건 `0`으로 채우는 것이 항상 정답은 아닙니다.

가격이 없음
≠ 가격이 0원

결측치 처리 방법은 데이터의 의미에 따라 결정해야 합니다.

공식 pandas 문서에서도 결측값 탐지, 제거, 대체를 각각 별도의 데이터 처리 단계로 설명합니다.

15. 중복 데이터 제거하기

웹 크롤링이나 여러 파일을 합치는 과정에서는 중복 데이터가 생길 수 있습니다.

import pandas as pd

data = {
    "상품명": [
        "키보드",
        "마우스",
        "키보드",
        "모니터"
    ],
    "가격": [
        35000,
        25000,
        35000,
        250000
    ]
}

df = pd.DataFrame(data)

중복 여부 확인하기

print(df.duplicated())

결과:

0    False
1    False
2     True
3    False
dtype: bool

중복 행 개수 확인하기

print(df.duplicated().sum())

중복 행 제거하기

df = df.drop_duplicates()

특정 열을 기준으로 중복 제거하기

df = df.drop_duplicates(
    subset=["상품명"]
)

기본적으로 첫 번째 데이터를 남깁니다.

마지막 데이터를 남기려면 다음과 같이 작성합니다.

df = df.drop_duplicates(
    subset=["상품명"],
    keep="last"
)

모든 중복 항목을 제거하려면 다음과 같이 작성합니다.

df = df.drop_duplicates(
    subset=["상품명"],
    keep=False
)

16. 문자열 데이터 정리하기

실제 데이터에는 불필요한 공백과 대소문자 차이가 자주 존재합니다.

data = {
    "상품명": [
        " 키보드 ",
        "MOUSE",
        " usb hub ",
        "Monitor"
    ]
}

df = pd.DataFrame(data)

앞뒤 공백 제거하기

df["상품명"] = (
    df["상품명"]
    .str.strip()
)

소문자로 변환하기

df["상품명"] = (
    df["상품명"]
    .str.lower()
)

대문자로 변환하기

df["상품명"] = (
    df["상품명"]
    .str.upper()
)

문자열 일부 변경하기

df["상품명"] = (
    df["상품명"]
    .str.replace(
        "usb",
        "USB",
        regex=False
    )
)

특정 문자로 시작하는 데이터 찾기

result = df[
    df["상품명"]
    .str.startswith("M")
]

특정 문자로 끝나는 데이터 찾기

result = df[
    df["상품명"]
    .str.endswith("드")
]

문자열 나누기

다음과 같은 데이터가 있다고 가정해 보겠습니다.

전자기기-키보드
전자기기-마우스
생활용품-텀블러
df[["대분류", "상품"]] = (
    df["분류"]
    .str.split(
        "-",
        n=1,
        expand=True
    )
)

`expand=True`를 사용하면 분리된 결과를 여러 열로 만들 수 있습니다.

17. 데이터 타입 변환하기

CSV 파일에서 숫자가 문자열로 읽히는 경우가 있습니다.

data = {
    "상품명": [
        "키보드",
        "마우스"
    ],
    "가격": [
        "35000",
        "25000"
    ]
}

df = pd.DataFrame(data)

print(df.dtypes)

결과에서 가격이 문자열 타입일 수 있습니다.

정수로 변환하기

df["가격"] = (
    df["가격"]
    .astype("int64")
)

실수로 변환하기

df["가격"] = (
    df["가격"]
    .astype("float64")
)

숫자로 안전하게 변환하기

데이터에 쉼표나 잘못된 문자가 포함되어 있을 수 있습니다.

data = {
    "가격": [
        "35000",
        "가격없음",
        "42000"
    ]
}

df = pd.DataFrame(data)

다음 코드는 오류를 발생시킬 수 있습니다.

df["가격"] = (
    df["가격"]
    .astype(int)
)

이럴 때 `pd.to_numeric()`을 사용합니다.

df["가격"] = pd.to_numeric(
    df["가격"],
    errors="coerce"
)

숫자로 변환할 수 없는 값은 결측치로 바뀝니다.

35000
NaN
42000

콤마가 들어간 가격은 먼저 제거합니다.

df["가격"] = (
    df["가격"]
    .str.replace(
        ",",
        "",
        regex=False
    )
)

df["가격"] = pd.to_numeric(
    df["가격"],
    errors="coerce"
)

18. 날짜 데이터 다루기

날짜 데이터가 문자열로 저장되어 있으면 연도별, 월별 분석이 어렵습니다.

data = {
    "주문일": [
        "2026-07-01",
        "2026-07-03",
        "2026-08-12"
    ],
    "매출": [
        35000,
        52000,
        28000
    ]
}

df = pd.DataFrame(data)

print(df.dtypes)

`주문일`이 문자열이라면 날짜 타입으로 변환합니다.

df["주문일"] = pd.to_datetime(
    df["주문일"],
    errors="coerce"
)

연도 추출하기

df["연도"] = (
    df["주문일"]
    .dt.year
)

월 추출하기

df["월"] = (
    df["주문일"]
    .dt.month
)

일 추출하기

df["일"] = (
    df["주문일"]
    .dt.day
)

요일 확인하기

df["요일번호"] = (
    df["주문일"]
    .dt.dayofweek
)

요일 번호는 다음과 같습니다.

월요일: 0
화요일: 1
수요일: 2
목요일: 3
금요일: 4
토요일: 5
일요일: 6

한글 요일로 변환해 보겠습니다.

weekday_names = {
    0: "월요일",
    1: "화요일",
    2: "수요일",
    3: "목요일",
    4: "금요일",
    5: "토요일",
    6: "일요일"
}

df["요일"] = (
    df["주문일"]
    .dt.dayofweek
    .map(weekday_names)
)

날짜 조건 검색하기

result = df[
    df["주문일"]
    >= "2026-07-02"
]

날짜 차이 계산하기

today = pd.Timestamp(
    "2026-07-20"
)

df["경과일"] = (
    today - df["주문일"]
).dt.days

문자열로 보이던 날짜가 시간의 축을 얻는 순간입니다. 달력이 데이터 분석 도구로 변신합니다. 📅

19. 그룹별 합계와 평균 계산하기

pandas의 핵심 기능 중 하나는 `groupby()`입니다.

판매 데이터를 카테고리별로 분석해 보겠습니다.

import pandas as pd

data = {
    "상품명": [
        "키보드",
        "마우스",
        "텀블러",
        "모니터",
        "손목 받침대"
    ],
    "카테고리": [
        "전자기기",
        "전자기기",
        "생활용품",
        "전자기기",
        "생활용품"
    ],
    "매출": [
        420000,
        500000,
        540000,
        750000,
        375000
    ],
    "판매량": [
        12,
        20,
        30,
        3,
        25
    ]
}

df = pd.DataFrame(data)

카테고리별 매출 합계

result = (
    df
    .groupby("카테고리")["매출"]
    .sum()
)

print(result)

결과 예시:

카테고리
생활용품     915000
전자기기    1670000
Name: 매출, dtype: int64

카테고리별 평균 판매량

result = (
    df
    .groupby("카테고리")["판매량"]
    .mean()
)

print(result)

결과를 DataFrame으로 만들기

result = (
    df
    .groupby(
        "카테고리",
        as_index=False
    )["매출"]
    .sum()
)

print(result)

`as_index=False`를 사용하면 그룹 기준 열이 일반 열로 유지됩니다.

여러 열을 함께 계산하기

result = (
    df
    .groupby(
        "카테고리",
        as_index=False
    )[["매출", "판매량"]]
    .sum()
)

print(result)

`groupby()`는 데이터를 그룹으로 나누고, 각 그룹에 연산을 적용한 뒤, 결과를 다시 결합하는 Split-Apply-Combine 방식으로 설명됩니다.

Split
카테고리별로 나누기

Apply
합계나 평균 계산하기

Combine
계산 결과 합치기

학교 운동회에서 반별로 줄을 세우고, 반마다 인원을 센 뒤, 전체 결과표를 만드는 과정과 비슷합니다.

20. 여러 통계를 한 번에 계산하기

`agg()`를 사용하면 여러 통계를 한 번에 계산할 수 있습니다.

한 열에 여러 통계 적용하기

result = (
    df
    .groupby("카테고리")["매출"]
    .agg([
        "sum",
        "mean",
        "min",
        "max"
    ])
)

print(result)

열마다 다른 통계 적용하기

result = (
    df
    .groupby("카테고리")
    .agg({
        "매출": [
            "sum",
            "mean"
        ],
        "판매량": [
            "sum",
            "max"
        ]
    })
)

print(result)

결과 열 이름 직접 지정하기

result = (
    df
    .groupby("카테고리")
    .agg(
        총매출=(
            "매출",
            "sum"
        ),
        평균매출=(
            "매출",
            "mean"
        ),
        총판매량=(
            "판매량",
            "sum"
        ),
        최고판매량=(
            "판매량",
            "max"
        )
    )
    .reset_index()
)

print(result)

`agg()`는 함수 이름, 함수 목록 또는 열별 함수 딕셔너리를 이용해 하나 이상의 집계 연산을 수행할 수 있습니다.

21. 피벗 테이블 만들기

Excel을 사용해 본 독자라면 피벗 테이블이라는 용어가 익숙할 수 있습니다.

pandas에서도 `pivot_table()`을 이용해 데이터를 요약할 수 있습니다.

import pandas as pd

data = {
    "지역": [
        "서울",
        "서울",
        "부산",
        "부산",
        "대전",
        "대전"
    ],
    "카테고리": [
        "전자기기",
        "생활용품",
        "전자기기",
        "생활용품",
        "전자기기",
        "생활용품"
    ],
    "매출": [
        500000,
        300000,
        450000,
        280000,
        250000,
        200000
    ]
}

df = pd.DataFrame(data)

지역과 카테고리별 매출 합계

pivot = pd.pivot_table(
    df,
    values="매출",
    index="지역",
    columns="카테고리",
    aggfunc="sum",
    fill_value=0
)

print(pivot)

결과 예시:

카테고리  생활용품  전자기기
지역
대전      200000  250000
부산      280000  450000
서울      300000  500000

전체 합계 추가하기

pivot = pd.pivot_table(
    df,
    values="매출",
    index="지역",
    columns="카테고리",
    aggfunc="sum",
    fill_value=0,
    margins=True,
    margins_name="전체"
)

print(pivot)

pandas의 `pivot_table()`은 스프레드시트 형태의 피벗 테이블을 DataFrame으로 생성하며, 인덱스·열·집계 함수·결측값 대체값 등을 지정할 수 있습니다.

`groupby()`와 피벗 테이블은 비슷한 분석을 수행할 수 있습니다.

groupby
→ 계산 결과를 행 중심으로 확인하기 편리

pivot_table
→ 행과 열을 교차해 표 형태로 비교하기 편리

22. 여러 DataFrame 합치기

실무에서는 데이터가 하나의 파일에만 들어 있지 않습니다.

예를 들어 상품 정보와 판매 정보가 따로 존재할 수 있습니다.

concat으로 위아래 연결하기

january = pd.DataFrame({
    "상품명": [
        "키보드",
        "마우스"
    ],
    "매출": [
        350000,
        250000
    ]
})

february = pd.DataFrame({
    "상품명": [
        "모니터",
        "USB 허브"
    ],
    "매출": [
        750000,
        290000
    ]
})

두 DataFrame을 위아래로 연결합니다.

result = pd.concat(
    [
        january,
        february
    ],
    ignore_index=True
)

print(result)

`ignore_index=True`를 사용하면 인덱스를 `0`부터 다시 생성합니다.

merge로 공통 열 기준 합치기

상품 정보가 다음과 같이 있다고 가정하겠습니다.

products = pd.DataFrame({
    "상품코드": [
        "P001",
        "P002",
        "P003"
    ],
    "상품명": [
        "키보드",
        "마우스",
        "모니터"
    ]
})

판매 정보는 다음과 같습니다.

sales = pd.DataFrame({
    "상품코드": [
        "P001",
        "P002",
        "P003"
    ],
    "판매량": [
        12,
        20,
        3
    ]
})

상품코드를 기준으로 합칩니다.

result = pd.merge(
    products,
    sales,
    on="상품코드",
    how="inner"
)

print(result)

결과:

상품코드  상품명  판매량
0  P001  키보드   12
1  P002  마우스   20
2  P003  모니터    3

merge의 결합 방식

방식설명
`inner`양쪽에 모두 존재하는 데이터
`left`왼쪽 DataFrame의 모든 데이터
`right`오른쪽 DataFrame의 모든 데이터
`outer`양쪽의 모든 데이터
result = pd.merge(
    products,
    sales,
    on="상품코드",
    how="left"
)

`concat()`은 데이터를 축 방향으로 연결할 때 사용하고, `merge()`는 하나 이상의 키를 기준으로 관계형 데이터베이스의 조인처럼 데이터를 결합할 때 사용합니다.

간단하게 기억하면 다음과 같습니다.

표를 위아래 또는 옆으로 붙인다
→ concat

공통 열을 기준으로 연결한다
→ merge

23. CSV 파일 읽고 저장하기

CSV는 쉼표로 값을 구분하는 텍스트 파일 형식입니다.

다음과 같은 `sales.csv` 파일이 있다고 가정하겠습니다.

상품명,카테고리,가격,판매량
키보드,전자기기,35000,12
마우스,전자기기,25000,20
텀블러,생활용품,18000,30

CSV 파일 읽기

import pandas as pd

df = pd.read_csv("sales.csv")

print(df)

`read_csv()`는 CSV 파일을 읽어 DataFrame으로 반환합니다.

필요한 열만 읽기

df = pd.read_csv(
    "sales.csv",
    usecols=[
        "상품명",
        "가격"
    ]
)

데이터 타입 지정하기

df = pd.read_csv(
    "sales.csv",
    dtype={
        "상품명": "string",
        "가격": "int64"
    }
)

결측값으로 처리할 값 지정하기

df = pd.read_csv(
    "sales.csv",
    na_values=[
        "없음",
        "-",
        "N/A"
    ]
)

CSV 파일 저장하기

df.to_csv(
    "result.csv",
    index=False,
    encoding="utf-8-sig"
)

`index=False`를 지정하지 않으면 DataFrame 인덱스가 별도의 열처럼 저장될 수 있습니다.

일부 데이터만 저장하기

expensive_products = df[
    df["가격"] >= 30000
]

expensive_products.to_csv(
    "expensive_products.csv",
    index=False,
    encoding="utf-8-sig"
)

24. Excel 파일 읽고 저장하기

Excel 파일 읽기

import pandas as pd

df = pd.read_excel(
    "sales.xlsx"
)

print(df)

특정 시트 읽기

df = pd.read_excel(
    "sales.xlsx",
    sheet_name="7월 판매"
)

여러 시트 읽기

sheets = pd.read_excel(
    "sales.xlsx",
    sheet_name=None
)

결과는 시트 이름을 키로 사용하는 딕셔너리 형태입니다.

for sheet_name, sheet_df in sheets.items():
    print(sheet_name)
    print(sheet_df.head())

Excel 파일 저장하기

df.to_excel(
    "result.xlsx",
    index=False
)

여러 시트로 저장하기

with pd.ExcelWriter(
    "sales_report.xlsx"
) as writer:
    df.to_excel(
        writer,
        sheet_name="전체 데이터",
        index=False
    )

    df.groupby(
        "카테고리",
        as_index=False
    )["매출"].sum().to_excel(
        writer,
        sheet_name="카테고리별 매출",
        index=False
    )

하나의 Excel 파일 안에 원본 데이터와 분석 결과를 각각 다른 시트로 저장할 수 있습니다.

25. 대용량 데이터 처리 방법

pandas는 대부분의 작업을 메모리에서 처리합니다.

파일이 컴퓨터 메모리보다 지나치게 크면 속도가 느려지거나 메모리 부족 오류가 발생할 수 있습니다.

공식 문서에서는 대용량 데이터 처리 방법으로 필요한 열만 불러오기, 효율적인 데이터 타입 사용하기, 청크 단위로 처리하기 등을 안내합니다.

필요한 열만 읽기

df = pd.read_csv(
    "large_sales.csv",
    usecols=[
        "상품명",
        "지역",
        "매출"
    ]
)

사용하지 않을 열을 처음부터 제외하면 메모리 사용량을 줄일 수 있습니다.

필요한 행만 읽기

df = pd.read_csv(
    "large_sales.csv",
    nrows=10000
)

처음 10,000개 행만 읽습니다.

데이터 타입 지정하기

df = pd.read_csv(
    "large_sales.csv",
    dtype={
        "지역": "category",
        "판매량": "int32"
    }
)

반복되는 문자열 값이 많은 열은 범주형 데이터 타입을 검토할 수 있습니다.

df["지역"] = (
    df["지역"]
    .astype("category")
)

메모리 사용량 확인하기

print(
    df.memory_usage(
        deep=True
    )
)

전체 사용량을 합산해 보겠습니다.

memory_bytes = (
    df.memory_usage(
        deep=True
    )
    .sum()
)

print(
    memory_bytes / 1024 / 1024,
    "MB"
)

청크 단위로 나누어 읽기

chunks = pd.read_csv(
    "large_sales.csv",
    chunksize=10000
)

total_sales = 0

for chunk in chunks:
    total_sales += (
        chunk["매출"]
        .sum()
    )

print(total_sales)

전체 파일을 한 번에 읽지 않고 10,000개 행씩 나누어 처리합니다.

첫 번째 10,000행 처리
→ 메모리에서 정리

다음 10,000행 처리
→ 메모리에서 정리

마지막까지 반복

대용량 데이터를 만났을 때 컴퓨터에게 “정신력으로 버텨!”라고 외치는 것보다, 작업을 작은 상자로 나누는 편이 훨씬 효과적입니다.

26. pandas로 간단한 그래프 그리기

pandas는 Matplotlib과 연동해 간단한 그래프를 만들 수 있습니다.

그래프 기능을 사용하려면 Matplotlib를 설치합니다.

pip install matplotlib

공식 pandas 설치 문서에서도 Matplotlib를 시각화 기능을 위한 선택적 의존성으로 안내합니다.

막대그래프 그리기

import pandas as pd
import matplotlib.pyplot as plt

data = {
    "카테고리": [
        "전자기기",
        "생활용품",
        "도서"
    ],
    "매출": [
        1500000,
        900000,
        600000
    ]
}

df = pd.DataFrame(data)

df.plot(
    x="카테고리",
    y="매출",
    kind="bar"
)

plt.tight_layout()
plt.show()

선 그래프 그리기

df.plot(
    x="카테고리",
    y="매출",
    kind="line",
    marker="o"
)

plt.tight_layout()
plt.show()

그룹 분석 결과 바로 그리기

category_sales = (
    df
    .groupby("카테고리")["매출"]
    .sum()
)

category_sales.plot(
    kind="bar"
)

plt.tight_layout()
plt.show()

pandas의 그래프 기능은 빠르게 데이터를 확인하는 데 편리합니다.

세밀한 디자인과 복잡한 차트를 만들 때는 Matplotlib나 다른 시각화 라이브러리를 직접 사용하는 것이 좋습니다.

27. 실전 미니 프로젝트: 판매 데이터 분석기

지금까지 배운 내용을 이용해 판매 데이터를 정리하고 분석하는 프로그램을 만들어 보겠습니다.

프로젝트 목표

다음 작업을 자동으로 수행합니다.

CSV 파일 읽기
→ 중복 제거
→ 숫자 타입 변환
→ 결측치 처리
→ 매출 계산
→ 카테고리별 통계 계산
→ 판매량 상위 상품 추출
→ Excel 보고서 저장

예제 CSV 파일

`product_sales.csv` 파일을 다음과 같이 준비합니다.

상품코드,상품명,카테고리,가격,판매량,주문일
P001,무선 키보드,전자기기,"35,000",12,2026-07-01
P002,텀블러,생활용품,"18,000",30,2026-07-02
P003,노트북 거치대,전자기기,"42,000",8,2026-07-03
P004,마우스,전자기기,"25,000",20,2026-07-04
P005,손목 받침대,생활용품,"15,000",25,2026-07-05
P006,USB 허브,전자기기,가격없음,16,2026-07-06
P001,무선 키보드,전자기기,"35,000",12,2026-07-01

전체 코드

from pathlib import Path

import pandas as pd


INPUT_FILE = Path("product_sales.csv")
OUTPUT_FILE = Path("sales_report.xlsx")


def load_data(file_path: Path) -> pd.DataFrame:
    """CSV 판매 데이터를 읽어 DataFrame으로 반환합니다."""
    if not file_path.exists():
        raise FileNotFoundError(
            f"입력 파일을 찾을 수 없습니다: {file_path}"
        )

    return pd.read_csv(
        file_path,
        dtype={
            "상품코드": "string",
            "상품명": "string",
            "카테고리": "string"
        }
    )


def clean_data(
    df: pd.DataFrame
) -> pd.DataFrame:
    """중복, 문자열, 숫자, 날짜 데이터를 정리합니다."""
    cleaned = df.drop_duplicates().copy()

    text_columns = [
        "상품코드",
        "상품명",
        "카테고리"
    ]

    for column in text_columns:
        cleaned[column] = (
            cleaned[column]
            .str.strip()
        )

    cleaned["가격"] = (
        cleaned["가격"]
        .astype("string")
        .str.replace(
            ",",
            "",
            regex=False
        )
    )

    cleaned["가격"] = pd.to_numeric(
        cleaned["가격"],
        errors="coerce"
    )

    cleaned["판매량"] = pd.to_numeric(
        cleaned["판매량"],
        errors="coerce"
    )

    cleaned["주문일"] = pd.to_datetime(
        cleaned["주문일"],
        errors="coerce"
    )

    cleaned = cleaned.dropna(
        subset=[
            "상품코드",
            "상품명",
            "가격",
            "판매량",
            "주문일"
        ]
    )

    cleaned["판매량"] = (
        cleaned["판매량"]
        .astype("int64")
    )

    cleaned["매출"] = (
        cleaned["가격"]
        * cleaned["판매량"]
    )

    cleaned["주문월"] = (
        cleaned["주문일"]
        .dt.to_period("M")
        .astype("string")
    )

    cleaned["판매등급"] = "일반"

    cleaned.loc[
        cleaned["판매량"] >= 20,
        "판매등급"
    ] = "인기"

    return cleaned.reset_index(
        drop=True
    )


def create_category_summary(
    df: pd.DataFrame
) -> pd.DataFrame:
    """카테고리별 판매 통계를 생성합니다."""
    summary = (
        df
        .groupby("카테고리")
        .agg(
            상품수=(
                "상품코드",
                "nunique"
            ),
            총판매량=(
                "판매량",
                "sum"
            ),
            총매출=(
                "매출",
                "sum"
            ),
            평균상품가격=(
                "가격",
                "mean"
            )
        )
        .reset_index()
        .sort_values(
            by="총매출",
            ascending=False
        )
    )

    summary["평균상품가격"] = (
        summary["평균상품가격"]
        .round(0)
        .astype("int64")
    )

    return summary


def create_top_products(
    df: pd.DataFrame,
    count: int = 5
) -> pd.DataFrame:
    """판매량이 높은 상품을 추출합니다."""
    return (
        df
        .sort_values(
            by=[
                "판매량",
                "매출"
            ],
            ascending=[
                False,
                False
            ]
        )
        .head(count)
        .reset_index(
            drop=True
        )
    )


def save_report(
    cleaned_df: pd.DataFrame,
    category_summary: pd.DataFrame,
    top_products: pd.DataFrame,
    output_file: Path
) -> None:
    """분석 결과를 여러 Excel 시트로 저장합니다."""
    with pd.ExcelWriter(
        output_file
    ) as writer:
        cleaned_df.to_excel(
            writer,
            sheet_name="정리된 데이터",
            index=False
        )

        category_summary.to_excel(
            writer,
            sheet_name="카테고리별 분석",
            index=False
        )

        top_products.to_excel(
            writer,
            sheet_name="판매량 TOP",
            index=False
        )


def main() -> None:
    try:
        raw_df = load_data(
            INPUT_FILE
        )

        print("원본 데이터")
        print(raw_df)
        print()

        cleaned_df = clean_data(
            raw_df
        )

        category_summary = (
            create_category_summary(
                cleaned_df
            )
        )

        top_products = (
            create_top_products(
                cleaned_df
            )
        )

        save_report(
            cleaned_df,
            category_summary,
            top_products,
            OUTPUT_FILE
        )

        print("데이터 정리가 완료되었습니다.")
        print(
            f"원본 행 수: {len(raw_df)}"
        )
        print(
            f"정리 후 행 수: {len(cleaned_df)}"
        )
        print(
            f"보고서 저장 위치: {OUTPUT_FILE.resolve()}"
        )

    except FileNotFoundError as error:
        print(error)

    except KeyError as error:
        print(
            f"필요한 열이 없습니다: {error}"
        )

    except Exception as error:
        print(
            f"처리 중 오류가 발생했습니다: {error}"
        )


if __name__ == "__main__":
    main()

실행 결과

프로그램을 실행하면 다음 파일이 생성됩니다.

sales_report.xlsx

Excel 파일은 세 개의 시트로 구성됩니다.

정리된 데이터
카테고리별 분석
판매량 TOP

프로그램 처리 흐름

product_sales.csv
        ↓
데이터 읽기
        ↓
중복 행 제거
        ↓
문자열 공백 정리
        ↓
가격의 쉼표 제거
        ↓
숫자 타입 변환
        ↓
잘못된 데이터 제거
        ↓
매출 계산
        ↓
카테고리별 집계
        ↓
판매량 상위 상품 추출
        ↓
sales_report.xlsx 저장

어지럽게 쌓여 있던 CSV 파일이 보고서로 다시 태어났습니다.

데이터가 양복을 입고 회의실에 들어가는 순간입니다. 👔📊

28. 자주 발생하는 오류

오류 1. pandas를 찾을 수 없음

ModuleNotFoundError: No module named 'pandas'

해결 방법

pip install pandas

Python 실행 환경이 여러 개라면 다음 명령어를 사용합니다.

python -m pip install pandas

또는 macOS와 Linux에서 다음 명령어를 사용할 수 있습니다.

python3 -m pip install pandas

오류 2. 파일을 찾을 수 없음

FileNotFoundError

현재 작업 폴더와 파일 위치를 확인합니다.

from pathlib import Path

print(Path.cwd())

파일 존재 여부도 확인할 수 있습니다.

file_path = Path(
    "sales.csv"
)

print(file_path.exists())

오류 3. 열 이름을 찾을 수 없음

KeyError: '가격'

실제 열 이름을 확인합니다.

print(df.columns.tolist())

열 이름 앞뒤에 공백이 있을 수도 있습니다.

df.columns = (
    df.columns
    .str.strip()
)

오류 4. 문자열을 숫자로 변환할 수 없음

ValueError: invalid literal for int()

다음처럼 잘못된 값이 들어 있을 수 있습니다.

35,000원
가격없음
-

문자를 정리한 뒤 `pd.to_numeric()`을 사용합니다.

df["가격"] = (
    df["가격"]
    .astype("string")
    .str.replace(
        ",",
        "",
        regex=False
    )
    .str.replace(
        "원",
        "",
        regex=False
    )
)

df["가격"] = pd.to_numeric(
    df["가격"],
    errors="coerce"
)

오류 5. Excel 엔진이 설치되지 않음

ModuleNotFoundError: No module named 'openpyxl'

다음 명령어를 실행합니다.

pip install openpyxl

또는 Excel 관련 선택적 의존성을 설치합니다.

pip install "pandas[excel]"

오류 6. 원본 데이터가 수정되지 않음

다음과 같은 연속 할당 코드를 사용했는지 확인합니다.

df[df["가격"] >= 30000]["등급"] = "고가"

다음처럼 `loc`를 사용합니다.

df.loc[
    df["가격"] >= 30000,
    "등급"
] = "고가"

오류 7. 날짜 변환 중 오류 발생

df["주문일"] = pd.to_datetime(
    df["주문일"],
    errors="coerce"
)

변환할 수 없는 날짜는 결측치가 됩니다.

print(
    df[df["주문일"].isna()]
)

어떤 원본 데이터가 잘못되었는지 확인한 뒤 수정합니다.

29. 연습 문제

문제 1

다음 딕셔너리를 DataFrame으로 변환해 보세요.

data = {
    "이름": [
        "민준",
        "서연",
        "지훈"
    ],
    "Python": [
        85,
        92,
        78
    ],
    "SQL": [
        80,
        88,
        95
    ]
}

문제 2

`Python` 점수가 80점 이상인 학생만 출력해 보세요.

힌트

df[df["Python"] >= 80]

문제 3

Python 점수와 SQL 점수의 평균을 계산해 `평균`이라는 새로운 열에 저장해 보세요.

힌트

df["평균"] = (
    df["Python"]
    + df["SQL"]
) / 2

문제 4

평균 점수가 높은 학생부터 정렬해 보세요.

힌트

df.sort_values(
    by="평균",
    ascending=False
)

문제 5

평균 점수가 90점 이상인 학생의 `등급`을 `A`로 설정해 보세요.

힌트

df.loc[
    df["평균"] >= 90,
    "등급"
] = "A"

문제 6

다음 데이터에서 결측치 개수를 확인해 보세요.

data = {
    "상품명": [
        "키보드",
        "마우스",
        "모니터"
    ],
    "가격": [
        35000,
        None,
        250000
    ]
}

힌트

df.isna().sum()

문제 7

가격의 결측치를 전체 평균 가격으로 채워 보세요.

힌트

average_price = (
    df["가격"]
    .mean()
)

df["가격"] = (
    df["가격"]
    .fillna(average_price)
)

문제 8

카테고리별 매출 합계를 계산해 보세요.

힌트

df.groupby(
    "카테고리"
)["매출"].sum()

문제 9

CSV 파일에서 `상품명`, `가격` 열만 읽어 보세요.

힌트

pd.read_csv(
    "sales.csv",
    usecols=[
        "상품명",
        "가격"
    ]
)

문제 10

분석 결과를 `result.xlsx` 파일로 저장해 보세요.

힌트

df.to_excel(
    "result.xlsx",
    index=False
)

30. 핵심 요약

pandas 불러오기

import pandas as pd

DataFrame 만들기

df = pd.DataFrame(data)

데이터 확인하기

df.head()
df.tail()
df.shape
df.columns
df.dtypes
df.info()
df.describe()

열 선택하기

df["상품명"]

df[
    [
        "상품명",
        "가격"
    ]
]

행 선택하기

df.loc[0]
df.iloc[0]

조건 검색하기

df[
    df["가격"] >= 30000
]

여러 조건 검색하기

df[
    (df["가격"] >= 30000)
    & (df["판매량"] >= 10)
]

정렬하기

df.sort_values(
    by="가격",
    ascending=False
)

새로운 열 만들기

df["매출"] = (
    df["가격"]
    * df["판매량"]
)

조건에 맞는 값 수정하기

df.loc[
    df["판매량"] >= 20,
    "등급"
] = "인기"

결측치 확인하기

df.isna().sum()

결측치 채우기

df["가격"] = (
    df["가격"]
    .fillna(0)
)

중복 제거하기

df = df.drop_duplicates()

그룹별 합계 계산하기

df.groupby(
    "카테고리"
)["매출"].sum()

CSV 읽기와 저장하기

df = pd.read_csv(
    "sales.csv"
)

df.to_csv(
    "result.csv",
    index=False
)

Excel 읽기와 저장하기

df = pd.read_excel(
    "sales.xlsx"
)

df.to_excel(
    "result.xlsx",
    index=False
)

31. 마무리

이번 시간에는 Python의 대표적인 데이터 분석 라이브러리인 pandas를 알아보았습니다.

pandas를 사용하면 다음 작업을 몇 줄의 코드로 수행할 수 있습니다.

* 표 형태의 데이터 생성* CSV와 Excel 파일 불러오기* 행과 열 선택* 조건별 데이터 검색* 정렬과 값 수정* 결측치와 중복 처리* 문자열과 날짜 정리* 그룹별 합계와 평균 계산* 피벗 테이블 생성* 여러 데이터 결합* 분석 결과 파일 저장

처음 pandas 코드를 보면 대괄호와 괄호가 복잡하게 느껴질 수 있습니다.

df[
    (df["카테고리"] == "전자기기")
    & (df["가격"] >= 30000)
]

하지만 문장처럼 해석하면 어렵지 않습니다.

df에서

카테고리가 전자기기이고

가격이 30,000원 이상인

행을 선택한다

pandas의 핵심은 수많은 명령어를 암기하는 것이 아닙니다.

데이터를 보며 다음 질문을 코드로 표현하는 것입니다.

어떤 열이 필요한가?

어떤 조건으로 행을 선택할 것인가?

빈 값은 어떻게 처리할 것인가?

무엇을 기준으로 그룹화할 것인가?

어떤 결과를 계산할 것인가?

웹 크롤링이 데이터를 세상 밖에서 데려오는 기술이라면, pandas는 데려온 데이터를 씻기고 줄을 세우고 이름표를 붙이는 기술입니다.

이제 수천 줄짜리 CSV 파일을 보더라도 너무 겁먹지 않아도 됩니다.

그 안에는 거대한 숫자 괴물이 사는 것이 아닙니다.

아직 정리되지 않은 표가 잠들어 있을 뿐입니다. 🐼📊

다음 편 예고

[Python 완전정복 시리즈 #25] Matplotlib 완벽 이해하기 | 숫자 데이터를 한눈에 보이는 그래프로 바꾸는 방법

pandas로 데이터를 정리했다면 이제 데이터가 들려주는 이야기를 눈으로 확인할 차례입니다.

다음 시간에는 선 그래프, 막대그래프, 원 그래프, 산점도와 여러 그래프 꾸미기까지 Python 데이터 시각화의 기본을 알아보겠습니다.

#Python #파이썬 #Python강좌 #파이썬기초 #pandas #판다스 #데이터분석 #데이터전처리 #CSV분석 #Excel자동화 #데이터프레임 #DataFrame #Series #결측치처리 #데이터정리 #파이썬데이터분석 #코딩공부 #프로그래밍

댓글

0

댓글을 불러오는 중입니다.