소프트웨어와 생활

표로 된 데이터 다루기

39줄을 읽어 36줄로 만들고
묶어서 세어 봅니다

학습 목표

  1. 표로 된 파일을 프로그램으로 읽어 줄 수와 칸 이름을 확인할 수 있다.
  2. 빠진 값(결측치) · 튄 값(이상치) · 겹친 줄을 차례로 걸러 내며 줄 수를 확인할 수 있다.
  3. 묶어 세어 나온 결과를 단정하지 않고 해석할 수 있다.

다룰 데이터

이번 시간에는 표로 된 데이터를 직접 다뤄 보겠습니다. 쓸 파일은 3-1-4.도서관-대출기록.csv이고, 이 강의노트 옆 code/ 폴더에 함께 들어 있습니다.

이 파일의 값은 지어낸 것이다

실제 어느 학교의 기록이 아닙니다. 수업에서 다루는 법을 익히려고 만든 값입니다. 그래서 여기서 나오는 결과로 어떤 학교에 대해서도 말할 수 없습니다. 익히는 것은 다루는 방법입니다.

어떻게 생겼나

쉼표로 칸을 나눈 파일입니다. 확장자가 .csv인 파일이 이런 꼴입니다. 첫 줄이 칸의 이름이고, 둘째 줄부터가 값입니다.

대출 기록 파일의 앞부분. 값은 수업용으로 지어낸 것이다.
날짜 학년 분야 권수
2026-03-01 1 예술 3
2026-03-03 3 역사 2
2026-03-06 2 사회 4
2026-03-07 2 문학 2
2026-03-19 (비어 있음) 과학 2

눈에 띄는 점이 있습니다. 이름이나 학번이 없습니다. 학년별로 세는 것이 목적이라 누구인지는 필요 없기 때문입니다. 분석에 필요 없는 이름과 학번은 받자마자 지운다는 원칙을 그대로 지킨 모습입니다.

답하려는 물음

  • 학년에 따라 빌리는 권수가 다른가?
  • 달에 따라 대출이 늘고 주는가?

물음을 먼저 적어 두어야 분석의 방향이 흔들리지 않습니다. 이 두 물음이 있어야 무엇을 묶어 셀지가 정해집니다.

스스로 확인

이 파일에 이름 칸이 있었다면 무엇을 더 할 수 있고, 무엇을 조심해야 할까요?

읽어 보기

파일을 읽는 데는 판다스(pandas)라는 도구를 씁니다. 표로 된 데이터를 다루기 위해 만들어진 것으로, 한 줄로 읽어 들이고 묶어 세는 일까지 해 줍니다.

표 = pd.read_csv(FILE)

print("줄 수:", len(표))
print("칸 이름:", list(표.columns))
print(표.head())
읽기 부분의 각 줄이 하는 일
코드 하는 일
pd.read_csv(FILE) 쉼표로 나뉜 파일을 읽어 표 모양으로 만들어 준다.
len(표) 줄 수를 센다. 분석에서 가장 먼저 확인할 값이다.
list(표.columns) 칸 이름을 늘어놓는다. 내가 아는 칸이 맞는지 확인한다.
표.head() 앞의 다섯 줄만 보여 준다. 눈으로 살펴보는 단계다.

실행해 보면 39줄이라고 나오고, 앞의 다섯 줄이 화면에 뜹니다. 그리고 벌써 한 가지가 눈에 띕니다.

학년이 1.0으로 나온다

분명 파일에는 1이라고 적었는데 화면에는 1.0으로 나옵니다. 빈칸이 하나 섞여 있기 때문입니다. 판다스는 빈칸을 NaN이라는 특별한 값으로 채우는데, 이것을 담으려면 그 칸 전체를 소수를 담을 수 있는 꼴로 바꿔야 합니다.

화면에 나온 모습이 이미 알려 준 것입니다. 「왜 1.0이지?」를 그냥 넘기지 않으면 빈칸을 계산하기 전에 찾아낼 수 있습니다.

스스로 확인

「학년」에는 빈칸이 없고 「권수」에만 빈칸이 하나 있는 파일이라면, 화면에서 무엇이 달라져 보일지 예상해 말해 보세요.

정제

빠진 값 · 튄 값 · 겹친 줄을 실제로 걸러 내겠습니다. 이때 한 가지를 걸러 낼 때마다 줄 수를 찍어 봅니다.

print("처음:", len(표))

표 = 표.dropna(subset=["학년"])            # 학년이 빈 줄을 뺀다
print("빈칸 뺀 뒤:", len(표))

표 = 표[표["권수"] <= 20]                   # 하루에 20권을 넘을 수는 없다고 보았다
print("튄 값 뺀 뒤:", len(표))

표 = 표.drop_duplicates()                   # 똑같은 줄이 두 번 들어간 것을 하나로
print("겹친 줄 뺀 뒤:", len(표))

표["학년"] = 표["학년"].astype(int)         # 빈칸을 뺐으니 이제 정수로 다룰 수 있다

실행해 보면 이렇게 나옵니다.

정제하는 과정에서 줄 수가 줄어드는 모습
무엇을 했나 남은 줄 줄어든 수
처음 39 —
학년이 빈 줄을 뺐다 38 1줄
권수가 20을 넘는 줄을 뺐다 37 1줄
똑같이 겹친 줄을 하나로 합쳤다 36 1줄

줄 수를 찍어 보는 것이 왜 중요한가

걸러 낸 뒤 얼마나 사라졌는지 모르면 결과를 믿을 수 없습니다. 여기서는 세 번 걸러 3줄만 빠졌습니다. 만약 39줄에서 12줄이 빠졌다면 그 데이터는 다시 봐야 합니다.

무엇을 뺐는지도 보고서에 적습니다. 「어떻게 정제했는가」를 밝히지 않은 분석은 남이 확인할 수가 없습니다.


「20을 넘으면 뺀다」는 우리가 정한 것이다

코드에 표["권수"] <= 20이라고 적었습니다. 이 20이라는 숫자는 어디서 왔을까요? 데이터가 알려 준 것이 아니라 우리가 정한 것입니다. 「하루에 한 사람이 20권을 넘게 빌리지는 않을 것」이라는 판단입니다.

이런 판단은 틀릴 수 있습니다. 학급문고를 통째로 빌려 간 날이 정말 있었다면, 우리는 진짜 있었던 일을 지운 셈입니다. 그래서 지우기 전에 확인하고, 확인할 수 없으면 정한 기준을 밝혀 둡니다.

똑같이 겹친 줄을 하나로 합친 것도 마찬가지입니다. 이 파일에는 번호 칸이 없어, 같은 날 같은 학년 두 사람이 같은 분야의 책을 같은 권수만큼 빌린 것인지 확인할 수 없습니다. 합친 것도 우리가 정한 기준입니다.

순서를 바꾸면 결과가 달라질까

빈칸 → 튄 값 → 겹친 줄 차례로 걸렀습니다. 순서를 바꿔도 될까요? 이 데이터에서는 같은 36줄이 나옵니다. 그러나 늘 그런 것은 아닙니다.

예를 들어 빈칸을 「평균으로 채우는」 방식을 쓴다면, 튄 값을 먼저 빼느냐 나중에 빼느냐에 따라 채워 넣는 평균이 달라집니다. 그래서 순서도 적어 두어야 합니다.

스스로 확인

기준을 20이 아니라 5로 바꾸면 몇 줄이 남을까요? 예상해 보고 코드를 고쳐 확인해 보세요. 어느 쪽이 더 알맞은 기준일까요?

묶어 세기

이제 물음에 답할 차례입니다. 첫 물음은 「학년에 따라 다른가」였습니다. 그러려면 학년이 같은 줄끼리 묶어 세면 됩니다.

학년별 = 표.groupby("학년")["권수"].agg(["count", "mean", "max"]).round(2)
print(학년별)

groupby("학년")이 학년이 같은 줄끼리 모으고, agg가 그 묶음마다 개수 · 평균 · 가장 큰 값을 구합니다. 결과는 이렇습니다.

학년별 대출 권수 요약. 값은 수업용으로 지어낸 자료에서 나온 것이다.
학년 건수 평균 권수 가장 많이 빌린 건
1학년 12 2.92 4권
2학년 12 2.83 5권
3학년 12 1.83 3권

건수 칸을 먼저 보세요. 셋 다 12건으로 같습니다. 만약 한 학년만 3건이었다면 그 학년의 평균은 믿기 어렵습니다. 평균을 보기 전에 건수를 보는 버릇이 여기서 쓰입니다.

결과를 읽으면 3학년이 눈에 띄게 적습니다. 1 · 2학년은 한 번에 3권 가까이 빌렸는데 3학년은 2권이 안 됩니다. 그런데 여기서 멈춰야 합니다 — 왜 그런지는 이 표가 말해 주지 않습니다.


달별로도 묶어 본다

표["달"] = 표["날짜"].str[5:7]
달별 = 표.groupby("달")["권수"].sum()
print(달별)

날짜는 2026-03-01처럼 적혀 있으므로, 달을 나타내는 여섯째 · 일곱째 글자(인덱스 5와 6)만 잘라 달로 삼았습니다. str[5:7]이 그 일을 합니다.

달별 대출 권수 합계 22 3월 21 4월 27 5월 21 6월

5월이 높다 — 여기서 무엇을 말할 수 있나

「5월에 시험이 있어서 도서관에 많이 왔다」고 적고 싶어집니다. 그런데 이 표에는 시험이 언제인지가 없습니다. 시험 이야기는 우리가 바깥에서 가져온 짐작입니다.

말할 수 있는 것은 여기까지입니다 — 「이 자료에서는 5월의 합계가 다른 달보다 높았다」. 그다음은 새 물음입니다. 「시험 일정을 함께 놓고 보면 어떨까」가 다음 분석의 시작이 됩니다.

해 보기 — 세 가지 더 물어보기

  1. 분야별로 묶어 세어 보세요. groupby("분야")로 바꾸면 됩니다. 어느 분야가 많은가요?
  2. 학년과 달을 함께 묶어 보세요. groupby(["학년", "달"])처럼 목록으로 넘기면 됩니다. 3학년의 5월은 어떤가요?
  3. 평균 대신 가운데 값(중앙값)으로 바꿔 보세요. "mean"을 "median"으로 고치면 됩니다. 달라지나요? 왜 그럴까요?

셋 다 결과를 적기 전에 예상을 먼저 적으세요. 예상과 다르게 나온 결과가 있으면 그 까닭을 찾아보세요.

스스로 확인

「3학년이 적다」를 단정하지 않는 문장으로 적어 보세요. 그리고 왜 그런지 알아보려면 어떤 데이터가 더 필요할까요?

핵심 정리

다룰 데이터

  • 쉼표로 칸을 나눈 파일이다. 첫 줄이 칸의 이름이다.
  • 이름과 학번이 없다. 학년별로 세는 데 필요 없기 때문이다.
  • 먼저 답하려는 물음을 적어야 무엇을 묶을지 정해진다.

읽어 보기

  • 읽자마자 줄 수 · 칸 이름 · 앞부분을 확인한다.
  • 학년이 1.0으로 나오는 것은 빈칸이 섞여 있다는 신호다.
  • 「왜 이렇게 보이지?」를 그냥 넘기지 않는다.

정제

  • 한 가지를 걸러 낼 때마다 줄 수를 찍어 본다. 39 → 36이었다.
  • 「20을 넘으면 뺀다」는 우리가 정한 기준이다. 틀릴 수 있으므로 밝혀 둔다.
  • 겹친 줄을 합친 것도 번호가 없어 확인할 수 없으므로 우리가 정한 기준이다.
  • 걸러 내는 순서에 따라 결과가 달라지는 경우도 있다.

묶어 세기

  • groupby로 같은 값끼리 모아 개수 · 평균 · 최댓값을 구한다.
  • 평균을 보기 전에 건수를 본다. 건수가 적으면 평균을 믿기 어렵다.
  • 「이 자료에서는 5월 합계가 높았다」까지가 자료가 말하는 것이다. 까닭은 새 물음이다.

확인 퀴즈

배운 내용을 확인해 봅시다.

Q1

학년이 1.0으로 나오는 것은 무엇을 알려 주나요?

Q2

걸러 낼 때마다 줄 수를 찍어 보는 까닭은?

Q3

학년별 표에서 평균보다 먼저 보아야 할 칸은?

Q4

5월 합계가 높게 나왔을 때 이 자료로 말할 수 있는 것은?