Pandas DataFrame 기초: 생성, 인덱싱, 입출력, 결측치, 데이터 조작

Alchemist·2026년 10월 6일

KT AIVLE

목록 보기
49/130

NumPy가 수치 배열 계산에 강하다면 Pandas는 행과 열이 있는 표 형태 데이터를 다루는 데 강하다.

CSV, Excel, 데이터베이스 조회 결과, API 응답처럼 실제 분석에서 만나는 데이터는 대부분 표 형태로 정리할 수 있다.

Pandas의 핵심 객체는 다음 두 가지다.

  • Series: 1차원 데이터
  • DataFrame: 2차원 표 데이터

이 글에서는 DataFrame 생성부터 인덱싱, 파일 입출력, 컬럼 변환, 결측치 처리, 정렬까지 현재 Pandas 3.x 기준으로 정리한다.


Pandas 불러오기

import pandas as pd

관례적으로 pd라는 별칭을 사용한다.


Series

Series는 인덱스를 가진 1차원 데이터다.

scores = pd.Series(
    [90, 85, 78],
    index=[
        "user_a",
        "user_b",
        "user_c",
    ],
)

print(scores)

인덱스로 접근:

print(
    scores.loc[
        "user_a"
    ]
)

위치로 접근:

print(
    scores.iloc[0]
)

DataFrame

여러 Series가 열 방향으로 결합된 표라고 이해할 수 있다.

df = pd.DataFrame(
    {
        "user_id": [
            101,
            102,
            103,
        ],
        "age": [
            24,
            31,
            28,
        ],
        "active": [
            True,
            False,
            True,
        ],
    }
)

print(df)

기본 정보 확인

처음 데이터를 받으면 구조부터 확인한다.

df.head()
df.tail()
df.shape
df.columns
df.index
df.dtypes

전체 요약:

df.info()

수치형 기술 통계:

df.describe()

문자열 컬럼의 dtype

최근 Pandas 3.x에서는 문자열 데이터가 예전처럼 항상 object로만 추론되지 않고 전용 문자열 dtype을 사용할 수 있다.

따라서 문자열 컬럼인지 확인할 때 단순히 다음 조건에만 의존하는 코드는 주의해야 한다.

df["name"].dtype == "object"

데이터의 의미와 Pandas의 dtype 시스템을 함께 확인하는 습관이 좋다.


컬럼 선택

하나의 컬럼:

ages = df["age"]

결과는 Series다.

여러 컬럼:

subset = df[
    [
        "user_id",
        "age",
    ]
]

결과는 DataFrame이다.


행 선택: loc

loc는 라벨 기반 선택이다.

df.loc[0]

행과 열을 함께 지정:

df.loc[
    0,
    "age",
]

여러 행과 컬럼:

df.loc[
    0:1,
    [
        "user_id",
        "age",
    ],
]

행 선택: iloc

iloc는 정수 위치 기반 선택이다.

df.iloc[0]

첫 번째 행, 두 번째 열:

df.iloc[
    0,
    1,
]

앞 두 행과 앞 두 열:

df.iloc[
    :2,
    :2,
]

loc와 iloc 차이

기능기준
loc인덱스/컬럼 라벨
iloc정수 위치

데이터를 다룰 때 두 방식을 명확하게 구분하는 것이 중요하다.


조건 필터링

df[
    df["age"] >= 28
]

여러 조건:

df[
    (df["age"] >= 28)
    & (df["active"])
]

Pandas에서는 and, or 대신 배열 단위 논리 연산자인 &, |를 사용한다.

괄호도 함께 작성하는 것이 중요하다.


.loc로 조건 필터링

필터링과 컬럼 선택을 동시에 할 수 있다.

df.loc[
    df["active"],
    [
        "user_id",
        "age",
    ],
]

값을 수정할 때도 .loc

df.loc[
    df["age"] >= 30,
    "segment",
] = "30+"

최근 Pandas 3.x에서는 chained assignment가 동작하지 않는 방향으로 정리되었기 때문에, 다음과 같은 코드는 피해야 한다.

# 피해야 하는 방식
df["segment"][
    df["age"] >= 30
] = "30+"

수정 대상 DataFrame에 한 번에 접근하는 .loc를 사용하는 것이 안전하다.

df.loc[
    df["age"] >= 30,
    "segment",
] = "30+"

새 컬럼 만들기

df["age_next_year"] = (
    df["age"] + 1
)

조건 기반 컬럼:

df["age_group"] = (
    df["age"]
    .ge(30)
    .map(
        {
            True: "30+",
            False: "under-30",
        }
    )
)

NumPy를 함께 사용할 수도 있다.

import numpy as np

df["age_group"] = np.where(
    df["age"] >= 30,
    "30+",
    "under-30",
)

assign()

기존 DataFrame을 직접 수정하지 않고 새 DataFrame을 만들고 싶을 때 사용할 수 있다.

result = df.assign(
    age_next_year=(
        df["age"] + 1
    )
)

메서드 체이닝에서 유용하다.


컬럼 삭제

df = df.drop(
    columns=[
        "age_next_year",
    ]
)

행 삭제:

df = df.drop(
    index=[0]
)

컬럼 이름 변경

df = df.rename(
    columns={
        "user_id": "id",
        "active": "is_active",
    }
)

전체 컬럼명을 직접 지정할 수도 있다.

df.columns = [
    "id",
    "age",
    "is_active",
]

인덱스 설정

특정 컬럼을 인덱스로 사용:

df = df.set_index(
    "user_id"
)

일반 컬럼으로 되돌리기:

df = df.reset_index()

기존 인덱스를 버리려면:

df = df.reset_index(
    drop=True
)

정렬

값 기준:

df = df.sort_values(
    by="age"
)

내림차순:

df = df.sort_values(
    by="age",
    ascending=False,
)

여러 컬럼:

df = df.sort_values(
    by=[
        "active",
        "age",
    ],
    ascending=[
        False,
        False,
    ],
)

인덱스 기준:

df = df.sort_index()

중복 데이터

df.duplicated()

중복 행 제거:

df = df.drop_duplicates()

특정 컬럼 기준:

df = df.drop_duplicates(
    subset=[
        "user_id",
    ],
    keep="last",
)

결측치

Pandas 데이터에는 NaN, NA, NaT 같은 결측값이 나타날 수 있다.

결측 여부:

df.isna()

컬럼별 개수:

df.isna().sum()

결측 행 제거

df = df.dropna()

특정 컬럼이 비어 있을 때만 제거:

df = df.dropna(
    subset=[
        "age",
    ]
)

결측값 채우기

df["age"] = (
    df["age"]
    .fillna(
        df["age"].median()
    )
)

범주형:

df["region"] = (
    df["region"]
    .fillna("unknown")
)

무조건 0으로 채우기보다 그 컬럼에서 결측이 무엇을 의미하는지 먼저 생각해야 한다.


값 치환

df["status"] = (
    df["status"]
    .replace(
        {
            "Y": "active",
            "N": "inactive",
        }
    )
)

map()

Series의 값을 변환한다.

label_map = {
    1: "paid",
    0: "unpaid",
}

df["payment_label"] = (
    df["payment"]
    .map(label_map)
)

함수 적용

Series:

def normalize_name(
    value,
):
    return (
        value
        .strip()
        .lower()
    )


df["name"] = (
    df["name"]
    .map(normalize_name)
)

DataFrame 단위 처리는 필요한 경우 메서드의 최신 동작을 확인하면서 사용하는 것이 좋다.

벡터화 연산으로 해결할 수 있다면 반복적인 Python 함수 적용보다 우선 고려할 수 있다.

예:

df["name"] = (
    df["name"]
    .str.strip()
    .str.lower()
)

문자열 컬럼 처리 .str

df["email"].str.lower()

공백 제거:

df["name"].str.strip()

포함 여부:

df[
    df["email"]
    .str.contains(
        "@example.com",
        na=False,
    )
]

CSV 읽기

import pandas as pd

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

자주 사용하는 옵션:

df = pd.read_csv(
    "orders.csv",
    usecols=[
        "order_id",
        "amount",
        "status",
    ],
)

날짜 컬럼은 읽은 후 명시적으로 변환하는 방법도 이해하기 쉽다.

df["ordered_at"] = (
    pd.to_datetime(
        df["ordered_at"]
    )
)

CSV 저장

df.to_csv(
    "orders-clean.csv",
    index=False,
)

index=False를 빼면 DataFrame 인덱스도 파일에 저장된다.


Excel 읽기

df = pd.read_excel(
    "sales.xlsx",
    sheet_name="Sales",
)

모든 시트를 읽으면 딕셔너리 형태로 받을 수 있다.

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

.xlsx 처리에는 환경에 따라 openpyxl 같은 Excel 엔진 패키지가 필요할 수 있다.


Excel 저장

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

여러 시트:

with pd.ExcelWriter(
    "report.xlsx"
) as writer:
    sales_df.to_excel(
        writer,
        sheet_name="Sales",
        index=False,
    )

    users_df.to_excel(
        writer,
        sheet_name="Users",
        index=False,
    )

JSON

df = pd.read_json(
    "users.json"
)

중첩된 API JSON을 표로 펼칠 때:

flat = pd.json_normalize(
    data
)

DataFrame 합치기: concat()

행 방향:

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

열 방향:

combined = pd.concat(
    [
        user_info,
        user_score,
    ],
    axis=1,
)

관계형 데이터 결합: merge()

사용자 정보:

users = pd.DataFrame(
    {
        "user_id": [
            1,
            2,
        ],
        "name": [
            "kim",
            "lee",
        ],
    }
)

주문 정보:

orders = pd.DataFrame(
    {
        "user_id": [
            1,
            1,
            2,
        ],
        "amount": [
            12000,
            35000,
            8000,
        ],
    }
)

결합:

result = users.merge(
    orders,
    on="user_id",
    how="left",
)

주요 방식:

  • inner
  • left
  • right
  • outer

SQL JOIN과 비슷하게 이해할 수 있다.


Copy-on-Write와 Pandas 3.x

Pandas 3.x에서는 Copy-on-Write가 기본 동작이 되어, 다른 DataFrame에서 만든 부분 집합을 수정했을 때 원본까지 우연히 바뀌는 오래된 모호한 동작을 줄였다.

핵심 규칙은 단순하다.

원본을 수정하고 싶다면 원본 DataFrame 자체를 한 번의 인덱싱으로 수정한다.

df.loc[
    df["amount"] < 0,
    "amount",
] = 0

chained assignment에 의존하지 않는 습관이 중요하다.


실전 예제: 주문 데이터 정리

import pandas as pd

orders = pd.DataFrame(
    {
        "order_id": [
            101,
            102,
            103,
            103,
        ],
        "amount": [
            12000,
            None,
            35000,
            35000,
        ],
        "status": [
            "PAID",
            "CANCELLED",
            "PAID",
            "PAID",
        ],
    }
)

중복 제거:

orders = (
    orders
    .drop_duplicates(
        subset="order_id"
    )
)

상태 문자열 정리:

orders["status"] = (
    orders["status"]
    .str.lower()
)

결측값:

orders["amount"] = (
    orders["amount"]
    .fillna(0)
)

결제 완료만 선택:

paid_orders = (
    orders.loc[
        orders["status"]
        == "paid"
    ]
)

이런 흐름이 실제 데이터 전처리의 기본 형태다.


핵심 정리

  • Pandas의 핵심 객체는 1차원 Series와 2차원 DataFrame이다.
  • 데이터를 받으면 head(), shape, dtypes, info(), describe()로 구조부터 확인하는 것이 좋다.
  • loc는 라벨 기반, iloc는 위치 기반 인덱싱이다.
  • 조건 필터링은 Boolean Series를 이용하며 여러 조건은 &, |로 결합한다.
  • Pandas 3.x에서는 원본 수정 시 chained assignment 대신 .loc로 한 번에 대입하는 패턴을 사용해야 한다.
  • 결측값은 무조건 삭제하거나 0으로 바꾸지 말고 데이터 의미를 먼저 확인해야 한다.
  • CSV와 Excel은 read_*, 저장은 to_* 계열 메서드로 처리한다.
  • concat()은 축 방향 결합, merge()는 키 기반 관계형 결합에 적합하다.
profile
html_programming_language

0개의 댓글