깨끗해진 데이터에서 의미를 찾아보자

지난 글에서는 우리가 사용하던 CSV 데이터를 직접 정리했습니다.
처음에는 이런 문제가 있었습니다.
결측치
중복 데이터
문자열 표기 불일치
자료형 문제
그리고 정제가 끝난 뒤 데이터는 다시 80명의 학습자 데이터가 되었습니다.
하지만 여기서 한 가지 질문이 생깁니다.
데이터를 깨끗하게 만들었으면 이제 무엇을 해야 할까?
데이터를 정리하는 것 자체가 목적은 아닙니다.
우리가 정말 알고 싶은 것은 데이터 안에 들어 있는 정보입니다.
예를 들면 이런 질문입니다.
어떤 과정을 듣는 학생이 가장 많을까?
평균적으로 일주일에 몇 시간 공부할까?
공부를 가장 많이 하는 학생은 누구일까?
합격한 학생은 불합격한 학생보다 정말 공부를 많이 했을까?
과정별 학습 시간에는 차이가 있을까?
이번 글에서는 Pandas를 이용해 이런 질문에 직접 답해보겠습니다.
그리고 마지막에는 지금까지 다듬은 데이터를 머신러닝이 사용할 수 있는 형태로 나누어보겠습니다.
정제된 데이터에서 시작하기
지난 글에서 만든 clean_df를 그대로 사용하겠습니다.
현재 데이터는 다음과 같습니다.
clean_df.shape
결과
(80, 9)
Column은 다음과 같습니다.
clean_df.columns
Index(['student_id', 'age', 'study_hours', 'attendance_rate',
'assignment_score', 'previous_score', 'course', 'city', 'passed'],
dtype='str')
만약 Python을 다시 실행해서 clean_df가 사라졌다면 지난 글에서 저장했던 정제된 CSV를 다시 불러오면 됩니다.
import pandas as pd
clean_df = pd.read_csv(
"ai_learning_dataset_clean.csv"
)
이제 분석을 시작해보겠습니다.
데이터 전체를 빠르게 요약하려면?
80개의 Row를 하나씩 보는 것은 현실적이지 않습니다.
clean_df
를 실행하면 전체 데이터를 볼 수는 있지만,
전체적인 특징이 무엇인가?
를 파악하기에는 오히려 어렵습니다.
Pandas에서는 숫자 데이터의 기본적인 통계를 한 번에 확인할 수 있는 describe()가 있습니다.
clean_df.describe()
출력을 조금 보기 좋게 만들기 위해 소수점 둘째 자리까지 표시해보겠습니다.
clean_df.describe().round(2)
일부 결과는 다음과 같습니다.
student_id age study_hours attendance_rate assignment_score previous_score passed
count 80.00 80.00 80.00 80.00 80.00 80.00 80.00
mean 1040.50 30.84 10.78 84.50 88.21 76.03 0.75
std 23.24 7.74 4.39 10.01 9.44 10.89 0.44
min 1001.00 19.00 1.30 61.00 55.70 50.30 0.00
25% 1020.75 24.00 8.55 77.05 82.02 68.25 0.75
50% 1040.50 30.00 10.75 84.65 89.00 76.00 1.00
75% 1060.25 38.00 13.52 91.80 96.43 84.78 1.00
max 1080.00 45.00 22.00 100.00 100.00 98.30 1.00
처음 보면 숫자가 많아 복잡해 보입니다.
하나씩 살펴보겠습니다.
count는 데이터 개수
count
는 값이 몇 개 있는지를 보여줍니다.
예를 들어:
study_hours
→ 80
attendance_rate
→ 80
입니다.
지난 글에서 결측치를 모두 처리했기 때문에 모든 값이 80개 존재합니다.
mean은 평균
mean
은 평균입니다.
예를 들어 학습 시간은:
study_hours
→ 10.78
입니다.
즉 우리 데이터에서 학생들은 평균적으로 일주일에 약 10.8시간 정도 공부합니다.
출석률 평균은
attendance_rate
→ 84.50
이므로 약 84.5%입니다.
min과 max
가장 작은 값과 가장 큰 값도 확인할 수 있습니다.
min
→ 1.3시간
max
→ 22시간
학습 시간은 위와 같습니다.
이것만으로도 학습 시간의 차이가 꽤 있다는 것을 알 수 있습니다.
하지만 여기서 바로
22시간은 이상치다.
라고 결론 내리면 안 됩니다.
지난 글에서도 이야기했듯이 큰 값과 잘못된 값은 같은 의미가 아닙니다.
이 데이터가 실제로 어떤 분포를 가지고 있는지는 다음 시각화 글에서 그래프로 확인해보겠습니다.
25%, 50%, 75%는 무엇일까?
describe()에는 조금 생소한 값도 있습니다.
25%
50%
75%
이것을 Quartile, 사분위수라고 합니다.
아주 간단하게 생각하면 데이터를 크기순으로 줄 세웠을 때의 위치입니다.
학습 시간을 보겠습니다.
25% → 8.55
50% → 10.75
75% → 13.52
학생들의 약 절반은 학습 시간이 10.75시간 이하라는 의미입니다.
여기서 50%는 우리가 지난 글에서도 사용했던 Median(중앙값)입니다.
아직 통계 자체를 깊게 공부할 필요는 없습니다.
지금은
describe()를 사용하면 데이터의 대략적인 범위를 빠르게 파악할 수 있다.
정도로 이해하면 충분합니다.
Category 데이터는 어떻게 살펴볼까?
describe()는 숫자 데이터를 파악할 때 편리합니다.
그런데 우리에게는 이런 Column도 있습니다.
course
city
숫자가 아니라 문자열입니다.
예를 들어 어떤 과정의 학생이 가장 많은지 알고 싶습니다.
이때 사용할 수 있는 것이
value_counts()
입니다.
과정별 학생 수 확인하기
print(clean_df["course"].value_counts())
결과
course
Python 30
AI 30
Data Science 20
Name: count, dtype: int64
이제 데이터를 하나씩 세어보지 않아도 됩니다.
Python
→ 30명
AI
→ 30명
Data Science
→ 20명
value_counts()는 Series의 각 고유 값이 몇 번 등장하는지 계산하는 함수입니다.
합격한 학생은 몇 명일까?
passed에도 사용할 수 있습니다.
clean_df["passed"].value_counts()
결과:
passed
1 60
0 20
Name: count, dtype: int64
우리 데이터에서는:
합격
→ 60명
불합격
→ 20명
입니다.
전체 80명 중 60명이 합격했습니다.
비율로 보고 싶다면?
이번에는 학생 수가 아니라 비율이 궁금합니다.
normalize=True를 사용해보겠습니다.
clean_df["passed"].value_counts(
normalize=True
)
결과는 대략 다음과 같습니다.
passed
1 0.75
0 0.25
여기에 100을 곱하면 백분율로 볼 수 있습니다.
clean_df["passed"].value_counts(
normalize=True
) * 100
결과
passed
1 75.0
0 25.0
합격
→ 75%
불합격
→ 25%
이 비율은 나중에 머신러닝에서도 중요하게 다시 등장합니다.
왜냐하면 AI에게 학습시킬 데이터에서 특정 정답이 지나치게 많거나 적으면 모델의 학습에도 영향을 줄 수 있기 때문입니다.
이 문제는 머신러닝에서 Class Imbalance(클래스 불균형)를 배울 때 다시 살펴보겠습니다.
지금은
정답 데이터의 분포도 확인할 필요가 있다.
는 정도로 기억해두면 좋습니다.
데이터를 크기순으로 보고 싶다면?
이번에는 이런 질문을 해보겠습니다.
공부를 가장 많이 한 학생은 누구일까?
Pandas에서는 sort_values()를 사용할 수 있습니다.
clean_df.sort_values(
"study_hours",
ascending=False
)
ascending=False는 큰 값부터 작은 값 순서로 정렬하겠다는 의미입니다.
필요한 Column만 가져오고 상위 5명만 확인해보겠습니다.
clean_df.sort_values(
"study_hours",
ascending=False
)[
[
"student_id",
"study_hours",
"attendance_rate",
"passed"
]
].head()
결과
student_id study_hours attendance_rate passed
1031 22.0 83.7 1
1062 20.5 61.0 0
1045 19.5 76.9 1
1004 19.2 77.1 1
1070 18.7 82.0 1
흥미로운 점이 하나 보입니다.
두 번째 학생은
study_hours
→ 20.5시간
으로 공부를 굉장히 많이 했지만,
passed
→ 0
불합격했습니다.
공부를 많이 하면 반드시 합격할까?
방금 데이터를 보면 그렇지 않습니다.
학습 시간
→ 20.5시간
출석률
→ 61%
결과
→ 불합격
인 학생이 있습니다.
이 학생은 중요한 힌트를 줍니다.
합격 여부는 학습 시간 하나만으로 결정되지 않을 수도 있다.
우리 데이터에는 여러 정보가 있습니다.
age
study_hours
attendance_rate
assignment_score
previous_score
머신러닝은 이런 여러 요소를 함께 이용해 패턴을 찾아갑니다.
하지만 아직 머신러닝으로 가지 않겠습니다.
Pandas로 조금 더 확인해보겠습니다.
합격한 학생과 불합격한 학생을 비교하고 싶다
우리가 알고 싶은 질문은 이제 이런 형태입니다.
합격한 학생들의 평균 학습 시간은 얼마일까?
불합격한 학생들과 차이가 있을까?
80명을 각각 나눠서 평균을 계산하는 것은 번거롭습니다.
바로 이런 상황에서 Pandas의 매우 중요한 기능인
groupby()
가 등장합니다.
groupby()는 왜 필요할까?
먼저 사람의 생각으로 문제를 해결해보겠습니다.
합격 여부를 기준으로 데이터를 나눕니다.
전체 학생
│
├── passed = 0
│ └── 불합격 학생
│
└── passed = 1
└── 합격 학생
그다음 각 그룹에서 평균을 계산합니다.
불합격 학생
→ 평균 학습 시간 계산
합격 학생
→ 평균 학습 시간 계산
그리고 결과를 다시 합칩니다.
Pandas 공식 문서에서는 GroupBy를 다음과 같은 흐름으로 설명합니다.
Split
↓
Apply
↓
Combine
즉 데이터를 그룹으로 나누고, 각 그룹에 계산을 적용하고, 결과를 다시 결합하는 방식입니다.
합격 여부에 따라 평균 학습 시간 비교하기
코드는 생각보다 간단합니다.
clean_df.groupby("passed")[
"study_hours"
].mean()
결과
passed
0 9.40
1 11.25
Name: study_hours, dtype: float64
우리 데이터에서는
불합격 학생
→ 평균 9.40시간
합격 학생
→ 평균 11.25시간
입니다.
합격 학생의 평균 학습 시간이 조금 더 높습니다.
하지만 이것만 보고
공부 시간이 길면 합격한다.
라고 결론 내릴 수는 없습니다.
평균적인 차이가 있다는 것과 원인이라는 것은 확연히 다른 이야기이기 때문입니다.
다른 데이터도 같이 비교해보자
이번에는 여러 Column을 한 번에 비교하겠습니다.
clean_df.groupby("passed")[
[
"study_hours",
"attendance_rate",
"assignment_score",
"previous_score"
]
].mean().round(2)
결과
study_hours attendance_rate assignment_score previous_score
passed
0 9.40 83.72 83.96 70.43
1 11.25 84.77 89.62 77.90
이제 조금 더 재미있는 패턴이 보입니다.
우리 데이터에서는 합격 학생의 평균이
study_hours
→ 더 높음
attendance_rate
→ 조금 더 높음
assignment_score
→ 더 높음
previous_score
→ 더 높음
으로 나타납니다.
이것이 바로 데이터를 분석하는 이유
지금까지 Pandas를 배우면서 했던 일을 생각해보겠습니다.
처음에는 단순히
CSV 읽기
에서 시작했습니다.
하지만 이제는
합격한 학생과
불합격한 학생 사이에
어떤 차이가 있을까?
라는 질문에 데이터를 이용해 답하고 있습니다.
이것이 단순한 파일 처리와 데이터 분석의 차이입니다.
과정별로도 비교해보자
이번에는
어떤 과정의 학생들이 가장 많이 공부할까?
를 알아보겠습니다.
clean_df.groupby("course")[
"study_hours"
].mean().round(2)
결과
course
AI 9.75
Data Science 11.30
Python 11.48
Name: study_hours, dtype: float64
우리 데이터에서는
Python
→ 11.48시간
Data Science
→ 11.30시간
AI
→ 9.75시간
입니다.
이것은 이 데이터셋 안에서 관찰되는 결과일 뿐입니다.
Python을 공부하는 사람이 원래 더 많이 공부한다.
는 일반적인 결론은 아닙니다.
우리가 가진 80명의 데이터에서 그런 결과가 나온 것입니다.
이 구분은 데이터 분석에서 굉장히 중요합니다.
데이터가 보여주는 것과 내가 해석하는 것은 다르다
예를 들어 다음 결과가 있다고 해보겠습니다.
합격 학생 평균 학습 시간
→ 11.25
불합격 학생 평균 학습 시간
→ 9.40
데이터가 직접 알려주는 것은 여기까지입니다.
그런데 우리가
공부를 많이 하면 무조건 합격한다.
라고 말하는 순간 해석이 들어갑니다.
실제로는
출석률
과제 점수
이전 시험 점수
개인의 학습 능력
과정의 난이도
등 여러 요인이 있을 수 있습니다.
그래서 좋은 데이터 분석은
숫자 계산
에서 끝나지 않습니다.
이 숫자가 실제로 무엇을 의미하는가?
까지 생각해야 합니다.
이제 Pandas를 왜 배웠는지 다시 생각해보자
NumPy에서는 숫자로 이루어진 Array를 배웠습니다.
NumPy
빠른 수치 계산
벡터
행렬
Array
그리고 Pandas에서는 현실의 데이터를 다뤘습니다.
Pandas
CSV
행과 열
Column 이름
결측치
문자열
조건 필터링
그룹별 분석
즉 전체 흐름은 다음과 같습니다.
현실의 데이터
↓
CSV
↓
Pandas
↓
데이터 확인
↓
필요한 데이터 선택
↓
데이터 정제
↓
데이터 분석
↓
머신러닝에 사용할 데이터
이제 마지막 단계만 남았습니다.
AI에게 데이터를 넘기려면?
우리가 앞으로 만들 머신러닝 모델의 목표를 하나 정해보겠습니다.
학생이 합격할지 예측하는 AI
를 만든다고 가정해보겠습니다.
우리가 가지고 있는 데이터에는
age
study_hours
attendance_rate
assignment_score
previous_score
...
passed
가 있습니다.
이 중 passed가 우리가 예측하고 싶은 값입니다.
Feature와 Label
머신러닝에서 가장 중요한 개념 중 하나가 등장합니다.
Feature
Label
아직 머신러닝을 배우지 않았으므로 아주 간단하게 이해해보겠습니다.
Feature
AI가 문제를 풀 때 참고할 정보입니다.
예를 들어
age
study_hours
attendance_rate
assignment_score
previous_score
와 같은 값입니다.
Label
AI가 맞혀야 하는 정답입니다.
우리 데이터에서는
passed
입니다.
age
study_hours
attendance_rate
assignment_score
previous_score
↓
AI 모델
↓
passed
와 같은 구조가 됩니다.
보통 X와 y라고 부른다
머신러닝 코드에서는 Feature 데이터를 보통
X
라고 부릅니다.
그리고 Label은
y
라고 부르는 경우가 많습니다.
따라서 우리의 데이터를 다음과 같이 나눌 수 있습니다.
feature_columns = [
"age",
"study_hours",
"attendance_rate",
"assignment_score",
"previous_score"
]
X = clean_df[feature_columns]
y = clean_df["passed"]
이제
X.head()
를 실행하면
age study_hours attendance_rate assignment_score previous_score
0 39 15.8 94.6 100.0 74.2
1 25 12.8 85.5 88.0 90.2
2 37 9.0 100.0 85.9 74.4
...
처럼 AI가 참고할 데이터가 만들어집니다.
그리고
y.head()
0 1
1 1
2 1
3 1
4 1
Name: passed, dtype: int64
처럼 정답만 남습니다.
왜 student_id는 Feature에서 뺐을까?
우리 데이터에는
student_id
도 있습니다.
그런데 Feature에는 넣지 않았습니다.
왜일까요?
student_id는 학생을 구분하기 위한 식별자(identifier)입니다.
예를 들어
1001
1002
1003
이라는 숫자가 크거나 작다고 해서 학생의 합격 가능성이 높아지는 것은 아닙니다.
즉 학생 번호 자체는 우리가 예측하려는 문제에 의미 있는 정보가 아닙니다.
따라서 모델에 무조건 모든 Column을 넣는 것이 아니라
이 Column이 실제로 예측에 의미가 있는 정보인가?
를 생각해야 합니다.
이것도 앞으로 머신러닝에서 계속 만나게 될 중요한 질문입니다.
그런데 course와 city는 왜 뺐을까?
이번에는 조금 다른 질문입니다.
course
city
는 의미가 없는 정보가 아닙니다.
오히려 합격 여부와 관계가 있을 수도 있습니다.
그런데 현재 값은
Python
AI
Data Science
Seoul
Busan
Incheon
처럼 문자열입니다.
많은 머신러닝 알고리즘에서는 이런 Category 데이터를 그대로 넣지 않고 숫자로 변환하는 과정이 필요합니다.
예를 들어, 아래의 값을
Python
AI
Data Science
모델이 처리할 수 있는 형태로 바꾸는 작업입니다.
이것을 Encoding(인코딩)이라고 합니다.
하지만 지금 여기까지 다루면 Pandas 글에서 머신러닝 전처리까지 너무 멀리 나가게 됩니다.
따라서 이번에는 숫자로 된 Feature만 사용하고,
Categorical Encoding
은 실제 머신러닝 모델을 만들 때 다시 배우겠습니다.
이제 데이터의 모습이 달라졌다
처음 우리가 가지고 있던 것은 단순한 CSV였습니다.
ai_learning_dataset.csv
그 안에는
결측치
중복
문자열 불일치
위와 같이 다양한 형태의 값이 있었습니다.
그리고 지금은
X
→ AI가 참고할 문제
y
→ AI가 맞힐 정답
까지 만들었습니다.
전체 과정을 다시 보면
CSV
↓
DataFrame
↓
데이터 구조 확인
↓
필요한 Row / Column 선택
↓
결측치 처리
↓
중복 제거
↓
문자열 정리
↓
통계 확인
↓
그룹별 비교
↓
Feature X
Label y
입니다.
Pandas를 배우는 이유가 이제 조금 더 선명해집니다.
Pandas는 AI 모델을 만드는 도구일까?
엄밀히 말하면 Pandas 자체가 머신러닝 모델을 만드는 핵심 라이브러리는 아닙니다.
대신 모델을 만들기 전의 데이터 작업에서 굉장히 중요한 역할을 합니다.
현실 데이터
↓
Pandas
↓
읽기 / 확인 / 정리 / 분석
↓
X / y
↓
머신러닝 라이브러리
↓
모델
앞으로 머신러닝에서는 Scikit-learn과 같은 라이브러리가 등장하게 됩니다.
https://scikit-learn.org/stable/
scikit-learn: machine learning in Python — scikit-learn 1.9.1 documentation
scikit-learn.org
하지만 그 라이브러리에 데이터를 넘기기 전에,
모델이 무엇을 학습할 것인지 준비하는 과정
에서 Pandas가 계속 등장하게 됩니다.
NumPy와 Pandas가 드디어 연결된다
앞에서 NumPy를 배울 때 이런 질문으로 시작했습니다.
Python List가 있는데 왜 NumPy가 필요할까?
그리고 Pandas를 시작할 때는
NumPy Array가 있는데 왜 Pandas가 필요할까?
라는 질문을 했습니다.
지금까지 공부한 것을 연결해보면 역할이 다릅니다.
Python:
데이터와 프로그램의 기본 구조
↓
NumPy:
빠른 수치 계산
Vector / Matrix / Array
↓
Pandas:
현실의 표 형태 데이터
정리 / 선택 / 분석
↓
Machine Learning:
데이터의 패턴 학습
예측
즉 하나가 다른 하나를 대체하는 것이 아닙니다.
각자 맡고 있는 역할이 있습니다.
Pandas에서 우리가 배운 것
Pandas 첫 글에서는
NumPy가 있는데 왜 DataFrame이 필요할까?
라는 질문으로 시작했습니다.
그리고 실제 CSV 파일을 가지고 여러 작업을 했습니다.
DataFrame과 Series
df
df["study_hours"]
표와 하나의 Column을 다루는 기본 구조를 배웠습니다.
CSV 읽기
pd.read_csv()
실제 데이터를 Python으로 가져왔습니다.
데이터 상태 확인
head()
shape
columns
dtypes
info()
분석하기 전에 데이터를 먼저 확인하는 습관을 만들었습니다.
데이터 선택
df["column"]
df.loc[...]
df.iloc[...]
df[조건]
필요한 데이터만 골라냈습니다.
데이터 정제
isna()
fillna()
duplicated()
drop_duplicates()
str.strip()
replace()
현실 데이터에 있는 문제를 직접 정리했습니다.
데이터 분석
describe()
value_counts()
sort_values()
groupby()
데이터를 단순한 표가 아니라 정보로 읽기 시작했습니다.
그리고 마지막으로
X = ...
y = ...
머신러닝으로 넘어갈 준비까지 마쳤습니다.
Pandas를 끝내며
Pandas에서 가장 중요한 것은 함수를 많이 외우는 것이 아니었습니다.
이번 시리즈에서 계속 반복한 흐름이 있습니다.
질문한다
↓
데이터를 확인한다
↓
필요한 부분을 선택한다
↓
문제를 발견한다
↓
데이터를 정리한다
↓
다시 확인한다
↓
비교하고 해석한다
그리고 이 과정은 앞으로 머신러닝에서도 그대로 반복됩니다.
AI가 아무리 발전하더라도 모델에 들어가는 데이터가 무엇인지 이해하지 못하면 좋은 결과를 얻기 어렵습니다.
그래서 Pandas는 단순한 CSV 처리 도구가 아니라,
현실의 데이터를 AI가 학습할 수 있는 형태로 연결해주는 다리
라고 이해하면 좋겠습니다.
그런데 아직 한 가지가 부족하다
지금 우리는 숫자를 이용해 데이터를 비교했습니다.
평균 학습 시간
→ 10.78
최소 학습 시간
→ 1.3
최대 학습 시간
→ 22.0
합격 여부에 따라서도 비교했습니다.
불합격 학생 평균 학습 시간
→ 9.40
합격 학생 평균 학습 시간
→ 11.25
하지만 숫자만 보고서는 한눈에 들어오지 않는 것이 있습니다.
예를 들어
대부분의 학생은 몇 시간 정도 공부하고 있을까?
22시간은 정말 특이한 값일까?
합격자와 불합격자의 학습 시간은 얼마나 겹칠까?
학습 시간이 늘어날수록 합격률도 올라가는 모습을 보일까?
이런 질문은 표보다 그래프로 보면 훨씬 쉽게 이해할 수 있습니다.
그래서 다음 단계에서는 데이터를 바라보는 방법을 바꿔보겠습니다.
숫자로 읽던 데이터
↓
눈으로 보는 데이터
다음 AI Journey에서는 데이터 시각화(Data Visualization)를 시작합니다.
첫 질문은 아주 단순합니다.
숫자로 볼 수 있는데 왜 굳이 그래프를 그릴까?
이 질문을 시작으로 우리가 지금까지 사용한 동일한 학습자 데이터를 직접 시각화해보겠습니다.
'AI Journey > Pandas' 카테고리의 다른 글
| Pandas 데이터 정제 완전 정복 — 결측치, 중복, 문자열 데이터 정리하기 (0) | 2026.09.06 |
|---|---|
| Pandas 데이터 선택하기 — loc, iloc과 조건 필터링 쉽게 이해하기 (0) | 2026.08.23 |
| Pandas CSV 불러오기 — 데이터를 받으면 가장 먼저 확인해야 할 것들 (0) | 2026.08.23 |
| Pandas란 무엇일까? NumPy Array가 있는데 DataFrame이 필요한 이유 (0) | 2026.08.17 |