본문 바로가기
IT

판다스(Pandas) - 데이터 변환 기능과 데이터 구조 최적화

by Dyudyu_Data 2026. 4. 12.
반응형

🔧 함수 매핑 (apply, map)

판다스에서 데이터 변환의 핵심은 함수 매핑입니다.

  • 데이터(Series, DataFrame)의 각 원소에 함수를 적용하는 것
  • 반복문(for) 없이도 각 값에 같은 작업을 할 수 있게 해 줌

📌 시리즈에 함수 적용

  • 시리즈의 각 원소에 함수 1번씩 적용
  • 사용자 정의 함수나 람다 함수 모두 가능

import pandas as pd
import numpy as np

# 샘플 데이터 생성
df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie', 'Diana'],
    'age': [25, 30, 35, 28],
    'salary': [50000, 60000, 70000, 55000]
})
display(df)

# 사용자 정의 함수 적용
def add_10(n):
    return n + 10

# 시리즈에 함수 적용
sr1 = df['age'].apply(add_10)
print("add_10 함수 적용 결과:")
print(sr1)

# 람다 함수 사용
sr2 = df['age'].apply(lambda n: n + 10)
print("\n람다 함수 적용 결과:")
print(sr2)

📌 데이터프레임에 함수 적용

# 통계 계산 함수
def calculate_stats(series):
    return pd.Series({
        'mean': series.mean(),
        'std': series.std(),
        'min': series.min(),
        'max': series.max()
    })

# 각 열에 함수 적용 (axis=0)
numeric_df = df[['age', 'salary']]
result_df = numeric_df.apply(calculate_stats, axis=0)
print("각 열에 통계 함수 적용:")
print(result_df)

# 각 행에 함수 적용 (axis=1)
result_sr = numeric_df.apply(lambda x: x.max() - x.min(), axis=1)
print("\n각 행의 최대값-최소값 차이:")
print(result_sr)

🔍 조건부 필터링과 분류

조건에 따른 데이터 필터링과 새로운 열 생성 방법입니다.

📌 조건부 열 추가

# 조건에 따른 분류
df['High_Performer'] = df.apply(lambda row: 'Yes' if row['salary'] > 55000 else 'No', axis=1)
print("고성과자 분류 추가:")
print(df)

# 나이가 평균보다 높은 사람들만 필터링
avg_age = df['age'].mean()
high_age_filter = df['age'] > avg_age
filtered_df = df[high_age_filter]
print(f"\n평균 나이({avg_age:.1f})보다 높은 사람들:")
print(filtered_df)

문자열 포맷팅(f-string) 문법 복습!

  • :.0f : 정수처럼 출력
  • :.1f : 소수점 1자리까지 실수(float) 형태로 출력
  • :.2f : 소수점 2자리까지 실수(float) 형태로 출력
  • :, : 천 단위로 콤마 붙여서 출력
  • :.1% : 퍼센트로 출력

⚡ pipe() 메소드와 체이닝

메소드 체이닝을 통한 연속적인 데이터 처리입니다.

# 데이터 처리 함수들
def add_bonus_column(df):
    df['bonus'] = df['salary'] * 0.1
    return df

def categorize_age(df):
    df['age_group'] = df['age'].apply(lambda x: 'Young' if x < 30 else 'Senior')
    return df

def calculate_total_compensation(df):
    df['total_comp'] = df['salary'] + df['bonus']
    return df

# 여러 함수를 체이닝으로 연결
result = (df.pipe(add_bonus_column)
           .pipe(categorize_age)
           .pipe(calculate_total_compensation))

print("파이프 체이닝 결과:")
print(result)

📊 그룹 연산 (GroupBy)

그룹별 집계와 변환의 강력한 기능입니다.

📌 기본 그룹화

# 타이타닉 데이터셋 예시
titanic_data = pd.DataFrame({
    'class': ['First', 'Second', 'Third', 'First', 'Second', 'Third'],
    'sex': ['male', 'female', 'male', 'female', 'male', 'female'],
    'age': [22, 38, 26, 35, 45, 29],
    'fare': [71.28, 71.28, 7.92, 53.10, 13.00, 7.75]
})

print("원본 타이타닉 데이터:")
print(titanic_data)

# 단일 기준 그룹화
grouped = titanic_data.groupby('class')
print("\n클래스별 평균 나이:")
print(grouped['age'].mean())

# 다중 기준 그룹화
grouped_two = titanic_data.groupby(['class', 'sex'])
print("\n클래스와 성별 그룹화:")
print(grouped_two.size())

# 집계 함수 적용
result = titanic_data.groupby('class').agg({
    'age': 'mean',
    'fare': ['min', 'max']
})
print("\n클래스별 집계 결과:")
print(result)
  • .size() : 그룹에 속한 행(row) 개수 세어주는 함수, 결측치(NaN) 포함, 그룹에 속한 행 수 그대로 세어줌, len(그룹) 이거랑 거의 같은 의미
  • .count() : 특정 컬럼의 값 개수, 결측치(NaN) 제외
  • .agg() : 그룹별로 여러 통계를 한 번에 계산

📌 변환(Transformation)

그룹별로 계산은 하지만, 결과의 “길이(shape)”는 원본과 동일하게 유지, 새 컬럼 만들 때 필수

# 그룹별 누적합
titanic_data['fare_cumsum'] = titanic_data.groupby('class')['fare'].cumsum()
print("그룹별 요금 누적합:")
print(titanic_data[['class', 'fare', 'fare_cumsum']])

# z-score 계산
titanic_data['age_zscore'] = titanic_data.groupby('class')['age'].transform(
    lambda x: (x - x.mean()) / x.std()
)
print("\n클래스별 나이 z-score:")
print(titanic_data[['class', 'age', 'age_zscore']])

🔗 데이터프레임 연결과 병합

여러 데이터프레임을 결합하는 방법입니다.

📌 연결(Concatenation)

  • 구조가 비슷한 데이터
  • 인덱스 기준으로 정렬
  • join 개념 없음
  • 위아래, 좌우로 붙이기
# 샘플 데이터프레임들
df1 = pd.DataFrame({
    'id': [1, 2, 3],
    'name': ['Alice', 'Bob', 'Charlie'],
    'score': [85, 90, 88]
})

df2 = pd.DataFrame({
    'id': [4, 5, 6],
    'name': ['Diana', 'Eve', 'Frank'],
    'score': [92, 87, 85]
})

print("df1:")
print(df1)
print("\ndf2:")
print(df2)

# 수직 연결
result1 = pd.concat([df1, df2], axis=0, ignore_index=True)
print("\n수직 연결 결과:")
print(result1)

# 수평 연결용 데이터
df3 = pd.DataFrame({
    'age': [25, 30, 35],
    'city': ['Seoul', 'Busan', 'Incheon']
})

result2 = pd.concat([df1, df3], axis=1)
print("\n수평 연결 결과:")
print(result2)
  • axis=0 : 밑에 붙이기
  • axis=1 : 옆에 붙이기
  • ignore_index=True : 인덱스 새로 붙이기

📌 병합(Merge)

  • 키로 결합
  • SQL의 JOIN과 동일
  • 행 개수 변할 수 있음
  • 관계형 데이터 결합
# 병합용 데이터프레임들
students = pd.DataFrame({
    'id': [1, 2, 3, 4],
    'name': ['Alice', 'Bob', 'Charlie', 'Diana'],
    'class': ['A', 'B', 'A', 'C']
})

grades = pd.DataFrame({
    'id': [1, 2, 3, 5],
    'subject': ['Math', 'Science', 'English', 'History'],
    'grade': [95, 88, 92, 85]
})

print("students 데이터:")
print(students)
print("\ngrades 데이터:")
print(grades)

# Inner Join
merge_inner = pd.merge(students, grades, how='inner', on='id')
print("\nInner Join 결과:")
print(merge_inner)

# Left Join
merge_left = pd.merge(students, grades, how='left', on='id')
print("\nLeft Join 결과:")
print(merge_left)
  • on = : 두 DataFrame을 연결할 “공통 기준 컬럼(키)”

< join 방식 (how) >

how 설명
inner 교집합 (기본)
left 왼쪽 기준
right 오른쪽 기준
outer 합집합

📋 피벗 테이블과 재구조화

데이터의 형태를 변환하는 핵심 도구입니다.

📌 피벗 테이블

그룹화 + 집계를 한 번에 하면서 테이블 모양까지 바꿔주는 함수

aggfunc = 중복 데이터를 어떻게 하나로 만들지 정함

< 자주 쓰는 aggfunc 종류 >

  • aggfunc='sum'
  • aggfunc='mean'
  • aggfunc='count'
  • aggfunc='min'
  • aggfunc='max'

📌 스택/언스택

index ↔ column을 서로 이동, MultiIndex를 다룰 때나 테이블 구조를 유연하게 바꿀 때 사용

  • stack (column → index)
  • unstack (index → column)
# 스택/언스택용 데이터
df_stack = pd.DataFrame({
    'A': [1, 2, 3],
    'B': [4, 5, 6],
    'C': [7, 8, 9]
}, index=['X', 'Y', 'Z'])

print("원본 데이터:")
print(df_stack)

# 스택: 열을 행으로 변환
stacked = df_stack.stack()
print("\n스택 결과 (열→행):")
print(stacked)

# 언스택: 행을 열로 변환
unstacked = stacked.unstack()
print("\n언스택 결과 (행→열):")
print(unstacked)

📌 Melt (wide → long)

여러 컬럼을 한 컬럼으로 접어서(long 형태) 세로로 늘리는 것

# Melt용 데이터 (Wide 형태)
wide_df = pd.DataFrame({
    'Name': ['Alice', 'Bob', 'Charlie'],
    'City': ['Seoul', 'Busan', 'Incheon'],
    'Math': [85, 90, 88],
    'Science': [92, 87, 85],
    'English': [78, 95, 92]
})

print("Wide 형태 데이터:")
print(wide_df)

# Wide to Long 형태 변환
melted_df = wide_df.melt(
    id_vars=['Name', 'City'],
    var_name='Subject',
    value_name='Score'
)
print("\nMelt 결과 (Wide → Long):")
print(melted_df)

< Wide 형태 >

  • 과목이 열(column)로 퍼져 있음
  • 사람 1명 = 행 1줄

< Long 형태 >

  • 분석/시각화/ML에 자주 씀
  • 과목이 행(row)으로 내려옴
  • “한 관측치 = (사람, 도시, 과목, 점수)” 같은 형태

< 각 파라미터 뜻 >

  • id_vars : 그대로 들고 갈 컬럼
  • var_name : 그대로 들고 갈 컬럼 제외하고 나머지 컬럼이 들어갈 곳의 컬럼 이름
  • value_name : 접힐 컬럼의 값이 들어갈 곳의 컬럼 이름

'IT' 카테고리의 다른 글

통계학 기초 - 2  (0) 2026.04.15
통계학 기초 - 1  (0) 2026.04.15
판다스(Pandas) - Series와 DataFrame  (1) 2026.04.11
파이썬 클래스(Class)?  (0) 2026.04.11
파이썬 람다, 일급 객체  (1) 2026.04.11