소개
데이터 엔지니어링 업무를 수행하다 보면, 데이터를 생성하여 분석가에게 전달하는 역할은 주로 데이터베이스 전문가나 데이터 스페셜리스트가 담당하기 때문에 행(Row)을 새로 만드는 일보다 파생 컬럼(Derived Column)을 생성하는 작업이 훨씬 많습니다. 하지만 모든 상황이 그렇지는 않습니다.
실제 데이터가 준비되기 전, 테스트나 프로토타이핑을 위해 샘플 행을 직접 생성해야 할 때가 종종 있습니다. 이번 글에서는 판다스 DataFrame에 새로운 행을 추가하는 다양한 방법과, 컬럼이 많은 복잡한 데이터셋에서 실수를 방지하기 위한 템플릿(Template) 활용 기법을 소개합니다.
기본 예제 데이터 준비
먼저 테니스 선수들의 그랜드슬램 우승 횟수를 담은 간단한 DataFrame을 생성해 보겠습니다.
import pandas as pd
import numpy as np
players_info = pd.DataFrame(data=[
{"players": "Roger Federer", "titles": 20},
{"players": "Rafael Nadal", "titles": 20},
{"players": "Novak Djokovic", "titles": 17},
{"players": "Andy Murray", "titles": 3}
], columns=["players", "titles"])
방법 1: .loc 인덱서를 이용한 행 추가 (In-place 수정)
.loc 속성은 기존 DataFrame을 직접 수정(In-place)합니다. 인덱스 위치를 지정하여 리스트, 딕셔너리, Series 등 다양한 형태의 데이터를 추가할 수 있습니다.
1) 리스트(List)로 행 추가
새로운 인덱스 번호(여기서는 4)에 리스트 형태의 데이터를 할당합니다. 순서가 컬럼 순서와 일치해야 합니다.
new_player = ['Dominic Thiem', 1]
players_info.loc[4] = new_player
2) 딕셔너리(Dictionary)로 행 추가
딕셔너리를 사용하면 컬럼명을 명시적으로 매핑할 수 있어 순서에 구애받지 않습니다. len(players_info)를 사용하면 현재 길이만큼의 인덱스, 즉 맨 끝에 행을 추가하게 됩니다.
new_player = {'players': 'Daniil Medvedev', 'titles': 0}
players_info.loc[len(players_info)] = new_player
3) 판다스 Series로 행 추가
Series 객체를 활용해 추가할 수도 있습니다. 인덱스(컬럼명)가 일치하면 값이 매핑됩니다.
players_info.loc[len(players_info)] = pd.Series({'players': 'Alexander Zverev', 'titles': 0})
.loc은 원본 DataFrame을 즉시 수정합니다. 별도의 변수에 할당할 필요가 없습니다.
방법 2: .append() 메서드 이용 (새로운 복사본 반환)
.append() 메서드는 원본 DataFrame을 변경하지 않고, 행이 추가된 새로운 DataFrame 객체를 반환합니다. 체이닝(Chaining)이나 원본 데이터 보존이 필요할 때 유용합니다.
주의사항: 인덱스 처리 (ignore_index)
기존 DataFrame에 사용자 정의 인덱스(문자열 등)가 있는 상태에서 딕셔너리나 Series를 추가하려 하면 TypeError가 발생할 수 있습니다. 이때 ignore_index=True 옵션을 주면 기존 인덱스를 무시하고 0부터 순차적인 정수 인덱스(RangeIndex)를 부여합니다.
# 인덱스가 'roger', 'nadal'... 등으로 설정된 DataFrame 예시
players_info = pd.DataFrame(data=[
{"players": "Roger Federer", "titles": 20},
{"players": "Rafael Nadal", "titles": 20},
{"players": "Novak Djokovic", "titles": 17},
{"players": "Andy Murray", "titles": 3}
], columns=["players", "titles"],
index=["roger", "nadal", "djokovic", "murray"])
# ignore_index=True 미지정 시 에러 발생
# new_df = players_info.append({'players': 'Daniil Medvedev', 'titles': 0})
# 올바른 사용법
new_df = players_info.append({'players': 'Daniil Medvedev', 'titles': 0}, ignore_index=True)
print("원본 (인덱스 유지):")
print(players_info)
print("\n신규 (인덱스 재부여):")
print(new_df)
여러 행을 한 번에 추가하기
리스트 안에 여러 Series나 딕셔너리를 담아 전달하면 여러 행을 한 번에 추가할 수 있습니다. Series를 사용할 경우 name 속성이 새 행의 인덱스가 됩니다.
player1 = pd.Series({'players': 'Alexander Zverev', 'titles': 0}, name='zverev')
player2 = pd.Series({'players': 'Dominic Thiem', 'titles': 1}, name='theim')
# 원본 인덱스 유지하며 추가
new_df_multi = players_info.append([player1, player2])
print(new_df_multi)
DataFrame.append() 메서드는 비권장(Deprecated)되었으며 향후 버전에서 제거될 예정입니다. 공식 문서에서는 pd.concat() 사용을 권장합니다.
권장 대체 코드:
# 단일 행 추가 시
new_row = pd.DataFrame([{'players': 'Daniil Medvedev', 'titles': 0}])
new_df = pd.concat([players_info, new_row], ignore_index=True)
# 여러 행 추가 시
new_rows = pd.DataFrame([
{'players': 'Alexander Zverev', 'titles': 0},
{'players': 'Dominic Thiem', 'titles': 1}
])
new_df = pd.concat([players_info, new_rows], ignore_index=True)
실전 팁: 컬럼이 많은 DataFrame에서 템플릿(Template) 활용하기
실무 데이터는 수십, 수백 개의 컬럼을 가집니다. 수동으로 딕셔너리를 작성하다 보면 컬럼명 오타, 누락, 자료형 불일치 등 실수가 빈번합니다. 이를 방지하기 위해 기존 행의 구조를 복사해 템플릿으로 만드는 기법을 소개합니다.
1단계: 기존 행을 딕셔너리로 변환 (구조 파악)
영화 메타데이터 예제 데이터셋(12개 컬럼)을 불러와 첫 번째 행을 기준으로 템플릿을 만듭니다.
# 예제 데이터 로드 (12개 컬럼, 4803행)
df = pd.read_csv("https://raw.githubusercontent.com/sasankac/TestDataSet/master/movies_data.csv")
# 첫 번째 행을 딕셔너리로 변환: {컬럼명: 값} 형태
columns_template = df.iloc[0].to_dict()
print(columns_template)
# 출력 예시:
# {'budget': 237000000, 'id': 19995, 'original_language': 'en',
# 'original_title': 'Avatar', 'popularity': 150.437577,
# 'release_date': '10/12/2009', 'revenue': 2787965087,
# 'runtime': 162.0, 'status': 'Released', 'title': 'Avatar',
# 'vote_average': 7.2, 'vote_count': 11800}
2단계: 자료형별 기본값으로 초기화된 템플릿 생성
딕셔너리 컴프리헨션(Dictionary Comprehension)을 이용해 문자열 컬럼은 빈 문자열(''), 숫자/날짜 컬럼은 결측치(np.nan)로 채워진 '빈 껍데기' 템플릿을 만듭니다.
import datetime
new_data_template = {}
for col, val in columns_template.items():
if isinstance(val, str):
new_data_template[col] = '' # 문자열: 빈 값
elif isinstance(val, (datetime.date, datetime.datetime)):
new_data_template[col] = np.nan # 날짜: NaN
else:
new_data_template[col] = np.nan # 숫자(int, float 등): NaN
print(new_data_template)
# 출력 예시:
# {'budget': nan, 'id': nan, 'original_language': '',
# 'original_title': '', 'popularity': nan, 'release_date': nan,
# 'revenue': nan, 'runtime': nan, 'status': '',
# 'title': '', 'vote_average': nan, 'vote_count': nan}
3단계: 템플릿 복사 후 필요 값만 채워 행 추가
이제 이 템플릿을 복사(.copy())하여 원하는 값만 수정한 뒤 DataFrame에 추가하면 컬럼 누락이나 오타 없이 안전하게 데이터를 넣을 수 있습니다.
# 템플릿 복사
new_movie = new_data_template.copy()
# 실제 데이터 입력
new_movie.update({
'id': 999999,
'title': 'New Movie Title',
'original_title': 'New Movie Title',
'original_language': 'en',
'status': 'Released',
'budget': 50000000,
'revenue': 0,
'popularity': 10.5,
'vote_average': 0.0,
'vote_count': 0,
'release_date': '2024-01-01',
'runtime': 120.0
})
# DataFrame에 추가 (pd.concat 권장)
new_row_df = pd.DataFrame([new_movie])
df_updated = pd.concat([df, new_row_df], ignore_index=True)
print(df_updated.tail(1))
요약 및 모범 사례 (Best Practices)
| 방법 | 특징 | 추천 상황 |
|---|---|---|
df.loc[len(df)] = data |
원본 직접 수정(In-place), 단일 행 추가에 직관적 | 간단한 스크립트, 소량 데이터 추가, 원본 보존 불필요 시 |
pd.concat([df, new_df]) |
새 객체 반환, 다중 행 추가 효율적, 공식 권장 방식 | 프로덕션 코드, 대량 데이터 적재, 원본 보존 필요 시, 최신 판다스 버전 호환성 |
| 템플릿 딕셔너리 활용 | 컬럼 누락/오타 방지, 자료형 일관성 유지 | 컬럼이 많은 복잡한 스키마, 반복적인 데이터 적재 파이프라인 구축 시 |
판다스에서 행을 추가하는 작업은 빈번하지만, 성능과 유지보수성을 고려해 pd.concat과 템플릿 패턴을 조합하는 것이 가장 견고한 접근법입니다.