본문으로 건너뛰기

pandas 100제: 판다스 연습문제와 풀이 · 11장

091–100 실전 종합

cut, map, idxmax(axis=1), qcut, cumsum, rolling, pct_change, shift, corr, agg 종합 연습문제

10분 읽기

이 장의 목차 13개

마지막 장은 구간 나누기, 누적·이동 평균 같은 실전 기법입니다. 91~94번은 학생 표, 95~98번은 방문자 표를 씁니다.

합계 점수를 0~199, 200~239, 240~300 세 구간으로 나눈 수직선
합계 점수를 0~199, 200~239, 240~300 세 구간으로 나눈 수직선

pd.cut의 구간은 기본적으로 왼쪽 끝은 빼고 오른쪽 끝은 포함합니다. 그래서 199점은 C, 200점은 B, 239점은 B, 240점은 A입니다. 첫 구간의 왼쪽 끝 0은 include_lowest=True가 있어야 포함됩니다. 경계값을 직접 넣어 보며 확인하는 습관을 들이세요.

7일 방문자 수에 3일 창을 밀며 평균을 내는 rolling 그림
7일 방문자 수에 3일 창을 밀며 평균을 내는 rolling 그림

rolling(3)은 길이 3짜리 창을 한 칸씩 밀면서 그 안의 값으로 계산합니다. 처음 두 날은 값이 3개가 되지 않아 NaN입니다. 창은 "3일"이 아니라 "3행"이므로, 날짜가 빠진 데이터라면 먼저 날짜를 채우거나 rolling("3D")처럼 기간으로 지정해야 합니다.

준비 코드와 데이터

노트북 첫 셀에 아래 준비 코드를 붙여 넣어 실행한 뒤 문제를 푸세요.

Pythonimport pandas as pd
import numpy as np

df = pd.DataFrame({
    'name': ['김민준', '이서연', '박지호', '최수아', '정민서', '강도윤', '윤하은', '임시우'],
    'class': ['A', 'B', 'A', 'C', 'B', 'C', 'A', 'B'],
    'kor': [85, 92, 58, 77, 66, 90, 73, 81],
    'eng': [78, 88, 64, 95, 59, 70, 82, 91],
    'math': [92, 75, 49, 88, 71, 66, 95, 60],
})
df['total'] = df['kor'] + df['eng'] + df['math']

daily = pd.DataFrame({
    'date': pd.date_range('2026-03-01', periods=7, freq='D'),
    'visitors': [120, 135, 128, 160, 155, 210, 198],
})

이 장 실습 노트북 내려받기 (.ipynb) — 준비 코드와 문제가 들어 있고 답 칸은 비어 있습니다. 코랩에서는 파일 → 노트 업로드로 엽니다.

준비 코드를 실행하면 df는 다음 표가 됩니다(8행 × 6열, 맨 왼쪽 숫자는 인덱스).

결과     name class  kor  eng  math  total
0  김민준     A   85   78    92    255
1  이서연     B   92   88    75    255
2  박지호     A   58   64    49    171
3  최수아     C   77   95    88    260
4  정민서     B   66   59    71    196
5  강도윤     C   90   70    66    226
6  윤하은     A   73   82    95    250
7  임시우     B   81   91    60    232

준비 코드를 실행하면 daily는 다음 표가 됩니다(7행 × 2열, 맨 왼쪽 숫자는 인덱스).

결과        date  visitors
0 2026-03-01       120
1 2026-03-02       135
2 2026-03-03       128
3 2026-03-04       160
4 2026-03-05       155
5 2026-03-06       210
6 2026-03-07       198

장 안의 문제는 번호 순서대로 이어집니다. 앞 문제에서 만든 열이나 변수를 다음 문제가 쓸 수 있으니, 중간부터 풀 때는 준비 코드와 앞의 필요한 문제를 먼저 실행하세요.

091. 구간 나누기

0~300점의 정수인 합계를 0~199, 200~239, 240~300 세 구간(C, B, A)으로 나눈 grade 열을 만드세요. 0점도 C에 포함하세요.

힌트 보기

pd.cut(값, bins=[0, 199, 239, 300], labels=[...], include_lowest=True).

정답 보기
Pythondf['grade'] = pd.cut(df['total'], bins=[0, 199, 239, 300], labels=['C', 'B', 'A'], include_lowest=True)
df[['name', 'total', 'grade']]
결과     name  total grade
0  김민준    255     A
1  이서연    255     A
2  박지호    171     C
3  최수아    260     A
4  정민서    196     C
5  강도윤    226     B
6  윤하은    250     A
7  임시우    232     B

풀이: include_lowest=True가 첫 경계 0도 포함시킵니다. 각 구간은 오른쪽 끝을 포함하며 범위 밖 값은 NaN입니다.

092. 값 바꿔 끼우기

반 이름을 담임 이름(A: 한선생, B: 오선생, C: 문선생)으로 바꾼 teacher 열을 만드세요.

힌트 보기

map({"A": "한선생", ...})으로 값을 바꿔 끼웁니다.

정답 보기
Pythondf['teacher'] = df['class'].map({'A': '한선생', 'B': '오선생', 'C': '문선생'})
df[['name', 'class', 'teacher']].head(4)
결과     name class teacher
0  김민준     A  한선생
1  이서연     B  오선생
2  박지호     A  한선생
3  최수아     C  문선생

풀이: 딕셔너리의 키와 반 이름을 대응시킵니다. 사전에 없는 반은 NaN이므로 실제 데이터에서는 미등록 값을 확인합니다.

093. 가장 잘한 과목

학생마다 국어·영어·수학 중 점수가 가장 높은 과목의 열 이름을 구하세요. 결과의 인덱스는 학생 이름으로 하세요.

힌트 보기

이름을 인덱스로 두고 과목 세 열만 남긴 뒤 idxmax(axis=1).

정답 보기
Pythondf.set_index('name')[['kor', 'eng', 'math']].idxmax(axis=1)
결과name
김민준    math
이서연     kor
박지호     eng
최수아     eng
정민서    math
강도윤     kor
윤하은    math
임시우     eng
dtype: str

풀이: 세 과목 중 최댓값이 있는 열 이름을 반환합니다. 동점이면 선택한 열 순서상 첫 과목입니다.

axis=1이면 행마다 최댓값의 열 이름을 돌려줍니다.

pandas 2.x에서는 마지막 줄이 dtype: object로 보입니다. 값은 같습니다.

094. 같은 인원으로 나누기

수학 점수를 사분위로 나눠 Q1~Q4를 붙이세요.

힌트 보기

pd.qcut(값, 4, labels=[...])은 인원이 비슷하도록 4등분합니다.

정답 보기
Pythonpd.qcut(df['math'], 4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
결과0    Q4
1    Q3
2    Q1
3    Q3
4    Q2
5    Q2
6    Q4
7    Q1
Name: math, dtype: category
Categories (4, str): ['Q1' < 'Q2' < 'Q3' < 'Q4']

풀이: 이 표에서는 각 구간 두 명입니다. 일반 데이터에서는 동점과 분위수 경계 중복 때문에 같은 인원을 보장하지 않습니다.

cut은 지정한 경계나 같은 폭의 구간으로, qcut은 분위수로 나눕니다. 동점이 많으면 인원이 같지 않거나 경계 중복 오류가 날 수 있습니다.

pandas 2.x에서는 값은 같고 표시만 다음과 같습니다.

결과0    Q4
1    Q3
2    Q1
3    Q3
4    Q2
5    Q2
6    Q4
7    Q1
Name: math, dtype: category
Categories (4, object): ['Q1' < 'Q2' < 'Q3' < 'Q4']

095. 누적 합계

daily의 날짜별 누적 방문자 수를 구하세요.

힌트 보기

cumsum().

정답 보기
Pythondaily['visitors'].cumsum()
결과0     120
1     255
2     383
3     543
4     698
5     908
6    1106
Name: visitors, dtype: int64

풀이: 현재 행 순서대로 누적합니다. 날짜가 뒤섞였다면 먼저 날짜순으로 정렬해야 합니다.

096. 이동 평균

daily의 3일 이동 평균 방문자 수를 소수 첫째 자리까지 구하세요.

힌트 보기

rolling(3).mean() 뒤에 .round(1).

정답 보기
Pythondaily['visitors'].rolling(3).mean().round(1)
결과0      NaN
1      NaN
2    127.7
3    141.0
4    147.7
5    175.0
6    187.7
Name: visitors, dtype: float64

풀이: 3은 여기서 세 행을 뜻합니다. 하루 한 행이고 빠진 날짜가 없어 3일 평균으로 해석할 수 있습니다.

앞 2일은 값이 모자라 NaN입니다.

097. 전날 대비 증감률

전날 대비 방문자 증감률(%)을 소수 첫째 자리까지 구하세요.

힌트 보기

pct_change()는 비율(0.125)을 줍니다. 100을 곱해 %로 바꾸세요.

정답 보기
Python(daily['visitors'].pct_change() * 100).round(1)
결과0     NaN
1    12.5
2    -5.2
3    25.0
4    -3.1
5    35.5
6    -5.7
Name: visitors, dtype: float64

풀이: pct_change는 비율을 반환하므로 100을 곱해 퍼센트로 바꿉니다. 이전 값이 없으면 NaN, 이전 값이 0이면 무한대가 될 수 있습니다.

098. 전날 값 옆에 놓기

전날 방문자 수 열 prev와 차이 diff를 만드세요.

힌트 보기

shift(1)은 한 칸 아래로 밉니다. 그 열과의 차이를 diff로 만듭니다.

정답 보기
Pythondaily['prev'] = daily['visitors'].shift(1)
daily['diff'] = daily['visitors'] - daily['prev']
daily
결과        date  visitors   prev  diff
0 2026-03-01       120    NaN   NaN
1 2026-03-02       135  120.0  15.0
2 2026-03-03       128  135.0  -7.0
3 2026-03-04       160  128.0  32.0
4 2026-03-05       155  160.0  -5.0
5 2026-03-06       210  155.0  55.0
6 2026-03-07       198  210.0 -12.0

풀이: shift(1)은 행 하나 전 값을 같은 행에 맞춰 놓습니다. 날짜를 하루 이동시키는 것이 아니므로 날짜 정렬과 누락 여부를 확인합니다.

099. 과목 간 상관관계

세 과목 점수의 상관계수 표를 구하세요.

힌트 보기

세 과목 열을 고른 뒤 corr().

정답 보기
Pythondf[['kor', 'eng', 'math']].corr().round(2)
결과       kor   eng  math
kor   1.00  0.52  0.31
eng   0.52  1.00  0.42
math  0.31  0.42  1.00

풀이: 숫자 열 사이의 선형 상관을 계산합니다. 표본이 작고 상관만으로 인과관계를 설명할 수는 없습니다.

100. 종합: 반별 리포트

반별 학생 수, 평균 합계(소수 첫째 자리), 최고 득점자 이름을 한 표로 만들고 평균 합계가 높은 반부터 보이세요.

힌트 보기

groupby("class").agg(학생수=(...), 평균합계=(...), 최고득점자=("total", lambda s: ...)) — 람다 안에서 s.idxmax()로 최고점 학생의 행 라벨을 얻습니다.

정답 보기
Pythonreport = df.groupby('class').agg(
    학생수=('name', 'size'),
    평균합계=('total', 'mean'),
    최고득점자=('total', lambda s: df.loc[s.idxmax(), 'name']),
)
report['평균합계'] = report['평균합계'].round(1)
report.sort_values('평균합계', ascending=False)
결과       학생수  평균합계 최고득점자
class                             
C           2     243.0     최수아
B           3     227.7     이서연
A           3     225.3     김민준

풀이: 그룹별 집계와 최고 합계의 이름을 결합합니다. 최고점 동점은 원래 표의 첫 학생이며 반올림한 평균을 기준으로 정렬합니다.

집계 함수 자리에 람다를 넣으면 원하는 계산을 그룹마다 할 수 있습니다.

이 장 요약

문법하는 일문제
pd.cut(s, bins=, labels=, include_lowest=True)경계값으로 구간091
s.map({옛값: 새값})값 바꿔 끼우기092
df.idxmax(axis=1)행마다 최댓값의 열 이름093
pd.qcut(s, 4, labels=)분위수로 구간094
s.cumsum()누적 합계095
s.rolling(3).mean()이동 평균096
s.pct_change()직전 대비 변화율097
s.shift(1)한 칸 밀기(전날 값)098
df.corr()상관계수 표099
groupby().agg(이름=(열, 함수))종합 리포트100

이 장의 점검

점수 경계 0·199·200·239·240·300과 동점, 날짜 누락을 따로 확인하세요. 예제에서 실행됐다는 사실만으로 다른 데이터의 조건까지 보장되지는 않습니다.

참고: pandas 공식 문서 · 문법이 기억나지 않으면 부록 치트시트를 찾아보세요.

전체 메뉴