pandas 100제: 판다스 연습문제와 풀이 · 10장
081–090 합치기와 모양 바꾸기
merge inner·left·outer, left_on·right_on, indicator, concat, melt, pivot, crosstab, explode 연습문제
이 장의 목차 13개
여러 표를 합치고, 넓은 표와 긴 표를 오가는 문제입니다.
merge는 키 값이 같은 행끼리 짝을 지어 옆으로 붙입니다. how="inner"는 양쪽에 모두 있는 키만, how="left"는 왼쪽 표의 행을 모두 남기고, how="outer"는 어느 한쪽에라도 있으면 남깁니다. 짝이 없는 칸은 NaN이 됩니다.
사람이 읽기 좋은 넓은 표(과목마다 열)와, 그래프·집계에 편한 긴 표(과목 이름이 한 열의 값)는 서로 바꿀 수 있습니다. melt는 열 이름을 값으로 내리고, pivot은 값을 다시 열 이름으로 올립니다.
준비 코드와 데이터
노트북 첫 셀에 아래 준비 코드를 붙여 넣어 실행한 뒤 문제를 푸세요.
Pythonimport pandas as pd
students = pd.DataFrame({'name': ['김민준', '이서연', '박지호', '최수아'],
'class': ['A', 'B', 'A', 'C']})
clubs = pd.DataFrame({'name': ['김민준', '박지호', '정민서'],
'club': ['축구', '밴드', '독서']})
scores = pd.DataFrame({'student': ['김민준', '이서연', '박지호', '최수아'],
'kor': [85, 92, 58, 77], 'eng': [78, 88, 64, 95]})
이 장 실습 노트북 내려받기 (.ipynb) — 준비 코드와 문제가 들어 있고 답 칸은 비어 있습니다. 코랩에서는 파일 → 노트 업로드로 엽니다.
준비 코드를 실행하면 students는 다음 표가 됩니다(4행 × 2열, 맨 왼쪽 숫자는 인덱스).
결과 name class
0 김민준 A
1 이서연 B
2 박지호 A
3 최수아 C
준비 코드를 실행하면 clubs는 다음 표가 됩니다(3행 × 2열, 맨 왼쪽 숫자는 인덱스).
결과 name club
0 김민준 축구
1 박지호 밴드
2 정민서 독서
준비 코드를 실행하면 scores는 다음 표가 됩니다(4행 × 3열, 맨 왼쪽 숫자는 인덱스).
결과 student kor eng
0 김민준 85 78
1 이서연 92 88
2 박지호 58 64
3 최수아 77 95
장 안의 문제는 번호 순서대로 이어집니다. 앞 문제에서 만든 열이나 변수를 다음 문제가 쓸 수 있으니, 중간부터 풀 때는 준비 코드와 앞의 필요한 문제를 먼저 실행하세요.
081. 공통 열로 합치기
students와 clubs를 name으로 합치세요(양쪽에 있는 학생만).
힌트 보기
students.merge(clubs, on="name").
정답 보기
Pythonstudents.merge(clubs, on='name')
결과 name class club
0 김민준 A 축구
1 박지호 A 밴드
풀이: inner 결합이므로 양쪽 표에 공통으로 있는 이름만 남습니다. 이름 중복이 없는 작은 예제입니다.
082. 왼쪽 기준 합치기
모든 학생을 남기고 동아리 정보를 붙이세요.
힌트 보기
merge에 how="left"를 더합니다.
정답 보기
Pythonstudents.merge(clubs, on='name', how='left')
결과 name class club
0 김민준 A 축구
1 이서연 B NaN
2 박지호 A 밴드
3 최수아 C NaN
풀이: left 결합은 왼쪽 학생을 유지합니다. 오른쪽 키가 중복되면 학생 행도 늘 수 있어 키의 유일성을 확인해야 합니다.
짝이 없는 동아리는 NaN이 됩니다.
083. 열 이름이 다를 때
students의 name과 scores의 student를 기준으로 합치세요.
힌트 보기
열 이름이 다르면 left_on="name", right_on="student".
정답 보기
Pythonstudents.merge(scores, left_on='name', right_on='student').drop(columns='student')
결과 name class kor eng
0 김민준 A 85 78
1 이서연 B 92 88
2 박지호 A 58 64
3 최수아 C 77 95
풀이: 이름이 다른 연결 열을 각각 지정한 뒤 중복된 이름 정보 열을 제거합니다.
084. 어느 쪽에 있었는지
양쪽 모든 행을 남기고, 행이 어디서 왔는지 표시하세요.
힌트 보기
how="outer"에 indicator=True를 더합니다.
정답 보기
Pythonstudents.merge(clubs, on='name', how='outer', indicator=True)
결과 name class club _merge
0 김민준 A 축구 both
1 박지호 A 밴드 both
2 이서연 B NaN left_only
3 정민서 NaN 독서 right_only
4 최수아 C NaN left_only
풀이: indicator의 both·left_only·right_only를 보면 연결 성공과 한쪽에만 있는 키를 구분할 수 있습니다.
085. 아래로 이어 붙이기
students 아래에 새 학생 한 명(이름 정민서, 반 B)을 붙이고, 인덱스를 0부터 다시 매기세요.
힌트 보기
새 학생을 한 행짜리 DataFrame으로 만든 뒤 pd.concat([students, new], ignore_index=True).
정답 보기
Pythonnew = pd.DataFrame({'name': ['정민서'], 'class': ['B']})
pd.concat([students, new], ignore_index=True)
결과 name class
0 김민준 A
1 이서연 B
2 박지호 A
3 최수아 C
4 정민서 B
풀이: 같은 이름의 열끼리 아래로 붙이고 ignore_index로 행 번호를 새로 만듭니다.
086. 옆으로 이어 붙이기
students와 scores의 kor, eng를 옆으로 붙이세요(행 순서가 같다고 가정).
힌트 보기
pd.concat([...], axis=1)은 옆으로 붙입니다.
정답 보기
Pythonpd.concat([students, scores[['kor', 'eng']]], axis=1)
결과 name class kor eng
0 김민준 A 85 78
1 이서연 B 92 88
2 박지호 A 58 64
3 최수아 C 77 95
풀이: axis=1의 concat은 행 라벨을 맞춥니다. 이 예제는 인덱스까지 같으며, 실제 이름 기준 결합은 merge가 적합합니다.
087. 넓은 표를 길게
scores를 student, subject, score 세 열의 긴 표로 바꾸세요.
힌트 보기
melt(id_vars="student", var_name="subject", value_name="score").
정답 보기
Pythonlong = scores.melt(id_vars='student', var_name='subject', value_name='score')
long
결과 student subject score
0 김민준 kor 85
1 이서연 kor 92
2 박지호 kor 58
3 최수아 kor 77
4 김민준 eng 78
5 이서연 eng 88
6 박지호 eng 64
7 최수아 eng 95
풀이: 학생 이름은 식별자로 두고 과목 열 이름과 점수를 두 열로 내려 긴 형태를 만듭니다.
088. 긴 표를 넓게
87번의 긴 표를 다시 학생별 과목 열로 펼치세요.
힌트 보기
pivot(index="student", columns="subject", values="score").
정답 보기
Pythonlong.pivot(index='student', columns='subject', values='score')
결과subject eng kor
student
김민준 78 85
박지호 64 58
이서연 88 92
최수아 95 77
풀이: pivot은 학생·과목 조합이 유일해야 합니다. 중복이 있으면 집계 방법을 정해 pivot_table을 사용합니다.
089. 교차표
모든 학생에게 동아리 정보를 붙인 뒤(왼쪽 결합), 반(행) × 동아리 가입 여부(열: 가입/미가입)별 학생 수를 교차표로 구하세요.
힌트 보기
왼쪽 결합 뒤 club이 비어 있지 않은지(notna())로 가입 여부를 만들고, pd.crosstab(행, 열)에 넣습니다.
정답 보기
Pythonjoined = students.merge(clubs, on='name', how='left')
pd.crosstab(joined['class'], joined['club'].notna().map({True: '가입', False: '미가입'}))
결과club 가입 미가입
class
A 2 0
B 0 1
C 0 1
풀이: 결합된 동아리 값이 있는지로 가입 여부를 만들고 반과 교차 집계합니다. 동아리 한 행이 학생 한 명이라는 예제 조건을 확인합니다.
090. 리스트를 행으로 펼치기
다음 표의 tags 열에는 리스트가 들어 있습니다. 태그 하나가 한 행이 되도록 펼치세요.
Pythont = pd.DataFrame({'post': [1, 2], 'tags': [['pandas', 'python'], ['sql']]})
힌트 보기
리스트를 행으로 펼치는 함수는 explode("열")입니다.
정답 보기
Pythont = pd.DataFrame({'post': [1, 2], 'tags': [['pandas', 'python'], ['sql']]})
t.explode('tags')
결과 post tags
0 1 pandas
0 1 python
1 2 sql
풀이: 리스트의 각 요소가 한 행이 됩니다. 다른 열의 값과 원래 인덱스는 반복됩니다.
이 장 요약
| 문법 | 하는 일 | 문제 |
|---|---|---|
a.merge(b, on="키") | 공통 키로 합치기(inner) | 081 |
merge(..., how="left") | 왼쪽 행 모두 유지 | 082 |
merge(left_on=, right_on=) | 키 열 이름이 다를 때 | 083 |
merge(how="outer", indicator=True) | 전부 남기고 출처 표시 | 084 |
pd.concat([a, b], ignore_index=True) | 위아래로 붙이기 | 085 |
pd.concat([a, b], axis=1) | 옆으로 붙이기(인덱스 기준) | 086 |
df.melt(id_vars=, var_name=, value_name=) | 넓은 표 → 긴 표 | 087 |
df.pivot(index=, columns=, values=) | 긴 표 → 넓은 표 | 088 |
pd.crosstab(행, 열) | 교차표(빈도) | 089 |
df.explode("열") | 리스트를 행으로 펼치기 | 090 |
이 장의 점검
merge 전후 행 수를 확인하세요. 연결 키가 오른쪽에서 유일해야 한다면 validate="many_to_one"을 지정해 중복을 오류로 발견할 수 있습니다.
참고: pandas 공식 문서 · 문법이 기억나지 않으면 부록 치트시트를 찾아보세요.