[Pandas] concat
정의
pd.concat(objs, axis=0) 는 여러 DataFrame/Series 를 세로 (axis=0) 또는 가로 (axis=1) 로 단순 연결. SQL 의 UNION (axis=0) 또는 column-level concatenation (axis=1).
key 매칭이 아닌 위치 기반 결합 이라 Pandas merge / Pandas join 과 구분된다.
사용 상황
- 행 단위 쌓기: 같은 구조의 DataFrame 여러 개를 하나로 합칠 때
- 가로 붙이기: 같은 index 를 공유하는 feature 그룹을 열 방향으로 결합할 때
- 계층 라벨 부여: 시기/그룹별 데이터를 묶어 Pandas MultiIndex 로 분석할 때
- df.append 대체: pandas 2.0 에서
append가 제거됨,pd.concat으로 완전 대체
merge / join 과 달리 key 매칭이 없으므로, 스키마가 같거나 의도적으로 다른 경우에 쓴다.
axis 동작 시각화
flowchart TD
subgraph "axis=0, 세로 연결"
A1["df1: 2행"] --> R1["결과: 4행 쌓기"]
A2["df2: 2행"] --> R1
end
subgraph "axis=1, 가로 연결"
B1["df1: 2열"] --> R2["결과: 4열 붙이기"]
B2["df2: 2열"] --> R2
end
join 파라미터 시각화
flowchart LR
subgraph "join='outer' 기본"
O1["df1: 열 a, b"] --> OR["결과: a, b, c 합집합 / NaN 채움"]
O2["df2: 열 a, c"] --> OR
end
subgraph "join='inner'"
I1["df1: 열 a, b"] --> IR["결과: a 교집합만"]
I2["df2: 열 a, c"] --> IR
end
세로 연결 (axis=0, 기본)
result = pd.concat([df1, df2])
import pandas as pd
df1 = pd.DataFrame({'name':['A','B'], 'age':[10,20]})
df2 = pd.DataFrame({'name':['C','D'], 'age':[30,40]})
print(pd.concat([df1, df2])) name age
0 A 10
1 B 20
0 C 30
1 D 40index 가 중복됨 (0, 1, 0, 1). ignore_index=True 로 재설정:
pd.concat([df1, df2], ignore_index=True)
# index: 0, 1, 2, 3
가로 연결 (axis=1)
pd.concat([df1, df2], axis=1)
# 컬럼이 옆으로 붙음, index 기준 정렬
import pandas as pd
left = pd.DataFrame({'a': [1, 2, 3]}, index=[0, 1, 2])
right = pd.DataFrame({'b': [10, 20, 30]}, index=[0, 1, 2])
print(pd.concat([left, right], axis=1)) a b
0 1 10
1 2 20
2 3 30index 가 다르면 NaN 이 생긴다:
left = pd.DataFrame({'a': [1, 2]}, index=[0, 1])
right = pd.DataFrame({'b': [10, 20]}, index=[1, 2])
pd.concat([left, right], axis=1)
# a b
# 0 1.0 NaN
# 1 2.0 10.0
# 2 NaN 20.0
컬럼이 다를 때
df1 = pd.DataFrame({'a': [1,2], 'b': [3,4]})
df2 = pd.DataFrame({'a': [5,6], 'c': [7,8]})
pd.concat([df1, df2])
# a: 1,2,5,6
# b: 3,4,NaN,NaN
# c: NaN,NaN,7,8
기본 join='outer', 합집합. join='inner' 면 교집합 컬럼만:
pd.concat([df1, df2], join='inner')
# 공통 컬럼 a 만 남음
keys (계층적 라벨)
pd.concat([df1, df2], keys=['Q1', 'Q2'])
# 결과의 index 가 MultiIndex: (Q1, 0), (Q1, 1), (Q2, 0), (Q2, 1)
여러 시기/그룹의 데이터를 한 번에 모아 분석할 때 유용:
combined = pd.concat([q1, q2, q3], keys=['Q1', 'Q2', 'Q3'])
# 특정 분기만 추출
combined.loc['Q2']
# 분기별 집계
combined.groupby(level=0).sum()
names 파라미터로 MultiIndex 레벨 이름도 지정:
pd.concat(
[df_2024, df_2025],
keys=[2024, 2025],
names=['year', 'original_idx'],
)
자주 쓰는 패턴
여러 파일을 하나로
import glob
dfs = [pd.read_csv(f) for f in glob.glob('data/*.csv')]
big = pd.concat(dfs, ignore_index=True)
새 행 추가 (pandas 2.x)
# pandas 1.x 의 df.append 는 pandas 2.0 에서 제거됨
new_row = pd.DataFrame([{'name': 'E', 'age': 50}])
df = pd.concat([df, new_row], ignore_index=True)
컬럼 추가 (가로 결합)
features = compute_features(df) # 별도 feature DataFrame
result = pd.concat([df, features], axis=1)
딕셔너리에서 concat
frames = {'train': df_train, 'val': df_val, 'test': df_test}
combined = pd.concat(frames)
# MultiIndex: ('train', 0), ('val', 0), ...
combined.loc['train'] # 학습 데이터만 추출
조건부 분기 처리 후 재결합
premium = df[df['tier'] == 'premium'].copy()
standard = df[df['tier'] != 'premium'].copy()
premium['discount'] = 0.2
standard['discount'] = 0.05
result = pd.concat([premium, standard]).sort_index()
Nullable dtype 유지 (pandas 2.x)
# int 컬럼에 NaN 이 섞여도 float 강등을 방지
df1 = pd.DataFrame({'count': pd.array([1, 2], dtype='Int64')})
df2 = pd.DataFrame({'count': pd.array([None, 4], dtype='Int64')})
pd.concat([df1, df2], ignore_index=True)
# count 컬럼: Int64 (nullable) 유지
성능
concat 은 전체 결과 메모리를 미리 할당 한 뒤 복사. 큰 데이터를 루프 안에서 반복 concat 하면 O(n²) 비용 가능.
# 느림: 루프 안에서 반복 concat
result = pd.DataFrame()
for df in dfs:
result = pd.concat([result, df])
# 빠름: 한 번에 리스트 전달
result = pd.concat(dfs, ignore_index=True)
대용량 concat 에서 dtype 을 미리 지정하면 메모리 절약:
dtypes = {'id': 'int32', 'value': 'float32', 'label': 'category'}
dfs = [pd.read_csv(f, dtype=dtypes) for f in files]
result = pd.concat(dfs, ignore_index=True)
TIP
pandas 2.x 에서는 pd.concat 결과가 항상 새 메모리를 할당한다. 임시 리스트를 concat 직후 del dfs 로 해제하면 피크 메모리를 줄일 수 있다.
pandas 2.x 변경점
| 항목 | pandas 1.x | pandas 2.x |
|---|---|---|
df.append | 지원 | 제거됨 (pd.concat 으로 대체) |
sort 기본값 | False | False (유지) |
| Copy-on-Write | 미지원 | 기본 활성화 |
| Nullable int + NaN concat | float 강등 | Int64 유지 가능 |
merge / join / concat 비교
| 동작 | 함수 |
|---|---|
| key 매칭 | merge |
| index 매칭 | join |
| 단순 연결 (위/옆) | concat |
함정
1. index 중복
pd.concat([df1, df2])
# 0, 1, 0, 1 -> .loc[0] 이 두 행 반환
# 해법: ignore_index=True 또는 reset_index(drop=True)
2. dtype 강등
# a 가 int 인 df1 에 NaN 이 포함된 df2 를 concat 하면 float 으로 강등
# 해법: Nullable dtype 사용
df1 = pd.DataFrame({'a': pd.array([1, 2], dtype='Int64')})
df2 = pd.DataFrame({'a': pd.array([None], dtype='Int64')})
pd.concat([df1, df2]) # Int64 유지
3. 컬럼 순서
# 합집합이지만 순서는 첫 DataFrame 우선
# df1 의 컬럼 + df2 의 새 컬럼 (뒤에 추가)
# 의도치 않은 순서를 원하면 .reindex(columns=원하는_순서) 로 정렬
4. axis=1 에서 index 불일치
left = pd.DataFrame({'a': [1, 2]}, index=[0, 1])
right = pd.DataFrame({'b': [10]}, index=[0])
pd.concat([left, right], axis=1)
# NaN 생성: right.b 의 index 1 은 NaN
WARNING
axis=1 concat 시 두 DataFrame 의 index 가 완전히 일치하지 않으면 NaN 이 생긴다. join='inner' 로 교집합 index 만 유지하거나, 사전에 index 를 정렬/재설정하라.
5. df.append() 제거 (pandas 2.0)
# AttributeError
df = df.append(new_row)
# 대체
df = pd.concat([df, new_row], ignore_index=True)
6. sort 파라미터
# pandas 2.x 기본: sort=False (첫 DataFrame 컬럼 순서 유지)
# sort=True 하면 알파벳 정렬
pd.concat([df1, df2], sort=False)
관련 위키
이 글의 용어 (8개)
- [Pandas] .loc / .ilocpandas
- 정의 - : 라벨 기반 인덱싱 (index/column 이름) - : 정수 위치 기반 인덱싱 (0-based) 이 두 indexer 가 pandas 선택의 표준. 만으로는 헷갈리…
- [Pandas] DataFramepandas
- 정의 은 2차원 레이블 테이블. 각 열이 , 모든 열이 같은 (행 라벨) 를 공유. SQL 테이블 / Excel 시트 / R data.frame 의 Python 대응체. 구조 시…
- [Pandas] groupbypandas
- 정의 는 데이터를 그룹으로 나누고 (split), 각 그룹에 함수를 적용 (apply), 결과를 합쳐 (combine) 새 DataFrame 으로 만드는 split-apply-c…
- [Pandas] joinpandas
- 정의 는 index 기반 결합 의 편의 API. 내부적으로는 와 동등하지만 더 짧다. pandas 에서 두 DataFrame 을 결합하는 세 가지 주요 함수: - : 컬럼 / 인…
- [Pandas] mergepandas
- 정의 는 두 DataFrame 을 SQL join 처럼 결합. 4 가지 join 타입 (inner / left / right / outer) 지원. 사용 상황 - 두 테이블 키 …
- [Pandas] MultiIndexpandas
- 정의 MultiIndex 는 여러 단계(level)의 라벨로 구성된 . SQL의 composite key와 비슷하며, 그룹화된 데이터를 자연스럽게 표현한다. , , 등이 자동으로…
- [Pandas] Nullable Types (Int64, boolean, string[pyarrow])pandas
- 정의 Nullable dtypes 는 NaN/None 을 타입 그대로 표현할 수 있는 pandas 의 새 타입 시스템. NumPy 기반의 (NaN 미허용) 의 한계를 극복. - …
- [Pandas] read_csv / to_csvpandas
- 정의 는 CSV(Comma-Separated Values) 또는 구분자 기반 텍스트 파일을 DataFrame 으로 읽는 함수. pandas 사용의 출발점. 반대 방향은 : Dat…
💬 댓글