본문으로 건너뛰기
김신건의 로그

[Pandas] explode

· 수정 · 📖 약 1분 · 517자/단어 #python #pandas #explode #reshape
Pandas explode, DataFrame.explode, list 컬럼 펼치기

정의

explode(column) 는 list / set / tuple 등의 시퀀스 값을 가진 컬럼을 각 원소가 별도 행 이 되도록 펼친다.

JSON / 로그 / 다중 태그 데이터 처리의 핵심.

사용 상황

  • JSON 배열 컬럼 정규화: items, tags, labels 처럼 배열 형태의 컬럼을 행 단위로 풀어야 할 때
  • 로그 파싱: 한 이벤트에 여러 속성이 list 로 저장된 구조 처리
  • 집계 역연산: groupby + agg(list) 로 만든 그룹 결과를 다시 원래 행 단위로 되돌릴 때
  • 태그/라벨 빈도 분석: 콤마 구분 문자열을 split 후 explode 해서 value_counts

explode 흐름 시각화

flowchart LR
    IN["한 행: user=Alice, tags=['py','pandas']"] --> EX["explode('tags')"]
    EX --> R1["행1: user=Alice, tags='py'"]
    EX --> R2["행2: user=Alice, tags='pandas'"]

기본

python
import pandas as pd
df = pd.DataFrame({
  'user': ['Alice', 'Bob', 'Charlie'],
  'tags': [['py', 'pandas'], ['py'], ['go', 'rust', 'ts']],
})
print(df.explode('tags'))
결과
      user    tags
0    Alice      py
0    Alice  pandas
1      Bob      py
2  Charlie      go
2  Charlie    rust
2  Charlie      ts

같은 index 가 반복됨 (0, 0, 1, 2, 2, 2). reset_index(drop=True) 로 깨끗하게:

df.explode('tags').reset_index(drop=True)

여러 컬럼 동시 explode (pandas 1.3+)

df.explode(['tags', 'scores'])
# 두 컬럼이 같은 길이여야 함
python
import pandas as pd
df = pd.DataFrame({
  'user':   ['Alice', 'Bob'],
  'tags':   [['py', 'pandas'], ['go']],
  'scores': [[90, 85],         [70]],
})
print(df.explode(['tags', 'scores']).reset_index(drop=True))
결과
    user    tags scores
0  Alice      py     90
1  Alice  pandas     85
2    Bob      go     70

빈 list / NaN 처리

df = pd.DataFrame({'x': [[1, 2], [], None]})
df.explode('x')
#    x
# 0  1
# 0  2
# 1  NaN     <- 빈 list 는 NaN 한 행
# 2  NaN     <- None 도 NaN

빈 리스트를 아예 제거하려면:

df.explode('x').dropna(subset=['x'])

str.split + explode

문자열을 list 로 분리 후 explode 는 가장 흔한 패턴:

df = pd.DataFrame({'tags_str': ['py,pandas', 'py', 'go,rust,ts']})
df['tags'] = df['tags_str'].str.split(',')
exploded = df.explode('tags')

assign 으로 체인:

(
    df
    .assign(tag=df['tags_str'].str.split(','))
    .explode('tag')
    .drop(columns=['tags_str'])
    .reset_index(drop=True)
)

groupby 의 역연산

# group -> list
grouped = df.groupby('user')['tag'].apply(list).reset_index()

# list -> row (explode)
grouped.explode('tag').reset_index(drop=True)

자주 쓰는 패턴

JSON 의 배열 펼치기

import pandas as pd
data = [
    {'order': 1, 'items': ['apple', 'banana']},
    {'order': 2, 'items': ['cherry']},
]
df = pd.DataFrame(data)
df.explode('items').reset_index(drop=True)

tags 빈도 분석

df = pd.DataFrame({
    'post_id': [1, 2, 3],
    'tags': ['python,pandas,data', 'python', 'go,rust'],
})
tag_counts = (
    df.assign(tag=df['tags'].str.split(','))
    .explode('tag')
    ['tag']
    .value_counts()
)

range 펼치기

df = pd.DataFrame({
    'start': [1, 5, 10],
    'end':   [3, 6, 12],
})
df['range'] = df.apply(lambda r: list(range(r['start'], r['end'] + 1)), axis=1)
df.explode('range').reset_index(drop=True)

중첩 JSON 정규화

import json

df = pd.DataFrame({
    'id': [1, 2],
    'attributes': ['[{"k":"color","v":"red"},{"k":"size","v":"M"}]', '[{"k":"color","v":"blue"}]'],
})
df['attrs_list'] = df['attributes'].apply(json.loads)
exploded = df.drop(columns=['attributes']).explode('attrs_list')
attrs = pd.json_normalize(exploded['attrs_list'])
result = pd.concat([exploded.drop(columns=['attrs_list']).reset_index(drop=True), attrs], axis=1)

이벤트 로그 파싱

# 한 이벤트에 여러 액션이 기록된 경우
df = pd.DataFrame({
    'session_id': [1001, 1002],
    'actions': [['click', 'scroll', 'purchase'], ['view', 'click']],
})
(
    df.explode('actions')
    .reset_index(drop=True)
    .rename(columns={'actions': 'action'})
)

성능

explode 는 내부적으로 NumPy 를 활용해 효율적으로 행을 복제한다.

# 피해야 할 패턴: 반복문으로 행 단위 처리
rows = []
for _, row in df.iterrows():
    for tag in row['tags']:
        rows.append({'user': row['user'], 'tag': tag})
result = pd.DataFrame(rows)   # 느림

# 권장: explode
result = df.explode('tags').reset_index(drop=True)

대용량 데이터에서 explode 전 불필요한 컬럼을 먼저 제거하면 메모리 절약:

subset = df.loc[:, ['user', 'tags']]
subset.explode('tags').reset_index(drop=True)

TIP

explode 결과의 행 수는 원본 리스트 길이의 합과 같다. 10 만 행의 DataFrame 에서 평균 list 길이가 5 라면 explode 후 50 만 행이 된다. 메모리와 후속 처리 비용을 미리 계산하고, 필터를 explode 전에 최대한 적용하라.

함정

1. index 중복

df.explode('tags')
# index: 0, 0, 1, 2, 2, 2
# .loc[0] 이 첫 user 의 모든 태그를 반환
df.explode('tags').reset_index(drop=True)   # 권장

2. dtype 강등

df = pd.DataFrame({'nums': [[1, 2], [3]]})
exploded = df.explode('nums')
exploded.dtypes
# nums: object 가 될 가능성
exploded['nums'] = pd.to_numeric(exploded['nums'])  # 다시 int

3. 컬럼이 list 가 아니면 그대로

df = pd.DataFrame({'x': [1, 2, 3]})
df.explode('x')   # 행 수 변화 없음

WARNING

컬럼의 dtype 이 object 라도 원소가 list 가 아니라 str 이면 explode 는 각 문자를 분리하지 않고 그냥 그대로 둔다. str.split 후 explode 를 해야 문자열을 분리할 수 있다.

4. 여러 컬럼 동시 explode 시 길이 불일치

df = pd.DataFrame({
    'tags':   [['a', 'b'], ['c']],
    'scores': [[10],        [20]],  # 첫 행이 길이 불일치
})
df.explode(['tags', 'scores'])   # ValueError 발생
# 두 컬럼의 list 길이가 반드시 일치해야 함

관련 위키

이 글의 용어 (6개)
[Pandas] Boolean Indexingpandas
정의 Boolean Indexing 은 True/False 의 Series 를 인덱서로 전달 해 행을 선택하는 패턴. pandas 의 가장 흔한 필터링 방법. 마스크 생성과 적용…
[Pandas] concatpandas
정의 는 여러 DataFrame/Series 를 세로 (axis=0) 또는 가로 (axis=1) 로 단순 연결. SQL 의 (axis=0) 또는 column-level conca…
[Pandas] groupbypandas
정의 는 데이터를 그룹으로 나누고 (split), 각 그룹에 함수를 적용 (apply), 결과를 합쳐 (combine) 새 DataFrame 으로 만드는 split-apply-c…
[Pandas] meltpandas
정의 는 의 역방향. wide-format → long-format 변환. 여러 컬럼을 두 컬럼 ( , ) 으로 합친다. 장기간 시계열 데이터나 시각화 라이브러리 (seaborn…
[Pandas] str regex (extract / split / replace)pandas
정의 의 정규식 기반 메서드. 패턴 추출, 분리, 치환 의 핵심. 의 정규식 특화 부분. 기본 문자열 메서드는 참고. 사용 상황 - 로그 문자열에서 IP, 날짜, 에러 코드 등을…
[Pandas] value_countspandas
정의 는 각 고유값의 등장 빈도 를 내림차순 Series 로 반환. SQL 의 . pandas 1.1+ 부터는 도 가능 (여러 컬럼 조합). 기본 <CodeWithOutput l…

이 개념을 다룬 위키 페이지 (1)

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기