본문 바로가기
IT

판다스(Pandas) - Series와 DataFrame

by Dyudyu_Data 2026. 4. 11.
반응형

📖 Part 1: 판다스 기초

판다스는 파이썬에서 데이터를 다루기 위한 라이브러리이다.
특히 표 형태의 데이터(엑셀, CSV, 데이터베이스 결과 등) 를 아주 편하게 처리할 수 있다.

판다스 = 파이썬의 엑셀 + 데이터 분석 도구

import pandas as pd

Series 이해하기

1차원 데이터 (한 열), 값(value) + 인덱스(index) 의 쌍, 엑셀로 치면 한 열, 딕셔너리나 리스트로 생성 가능

  • 데이터의 순서를 나타내는 이름표(인덱스) 제공
  • 딕셔너리와 비슷한 구조를 가진 데이터프레임의 컨테이너
# 리스트로 시리즈 만들기

list_data = pd.Series([85, 92, 78, 96], index = ['김철수', '이영희', '박민수', '최지연'])
print(list_data)
# 딕셔너리로 시리즈 만들기

dict_data = pd.Series({'김철수': 85, '이영희': 92, '박민수': 78, '최지연': 96})
print(dict_data)

Pandas Series 핵심 내장함수

# 기본 통계 함수
scores.mean() # 평균: 90.0
scores.median() # 중앙값: 90.0
scores.std() # 표준편차: 3.74
scores.var() # 분산: 14.0
scores.sum() # 합계: 450
scores.count() # 개수: 5

# 최대/최소값 함수
scores.max() # 최댓값: 95
scores.min() # 최솟값: 85
scores.idxmax() # 최댓값의 인덱스: '민수'
scores.idxmin() # 최솟값의 인덱스: '영희'

# 정보 확인 함수
scores.describe() # 요약 통계 (count, mean, std, min, 25%, 50%, 75%, max)
scores.info() # 데이터 타입과 메모리 정보
scores.shape # 크기: (5,)
scores.size # 요소 개수: 5

# 값 탐색 함수
scores.unique() # 고유값들: [90, 85, 95, 88, 92]
scores.value_counts() # 값별 빈도수
scores.nunique() # 고유값 개수: 5
scores.isin([90, 95]) # 특정 값 포함 여부 (불린 시리즈 반환)

# 정렬 함수
scores.sort_values() # 값 기준 정렬 (오름차순)
scores.sort_values(ascending=False) # 값 기준 정렬 (내림차순)
scores.sort_index() # 인덱스 기준 정렬

# 조건 함수
scores[scores > 90] # 90점 초과 학생들
scores.where(scores > 90) # 조건 만족 시 값, 아니면 NaN
scores.mask(scores < 90) # 조건 만족 시 NaN, 아니면 값

# 데이터 변환 함수
scores.apply(lambda x: x + 5) # 각 값에 5 더하기
scores.map({90: 'A', 85: 'B'}) # 값 매핑
scores.astype('float') # 데이터 타입 변환
scores.round(1) # 소수점 반올림

# 결측값 처리 함수
scores.isnull() # 결측값 확인 (불린 시리즈)
scores.notnull() # 비결측값 확인
scores.dropna() # 결측값 제거
scores.fillna(0) # 결측값을 0으로 채우기

# 누적 통계 함수
scores.cumsum() # 누적합: [90, 175, 270, 358, 450]
scores.cummax() # 누적 최댓값
scores.cummin() # 누적 최솟값
scores.cumprod() # 누적곱

# 순위/백분위 함수
scores.rank() # 순위 (1~5)
scores.quantile(0.5) # 50% 백분위수 (중앙값)
scores.quantile([0.25, 0.75]) # 25%, 75% 백분위수

📊 Part 2: DataFrame 마스터하기

DataFrame 생성과 구조

2차원 데이터, 여러 개의 Series 가 모인 것, 행(row)와 열(column)로 구성, 2차원 표 형태의 자료구조

  • 여러 개의 Series가 합쳐진 2차원 배열 또는 행렬
  • 행과 열의 종류의 속성을 갖는 일련의 데이터

🔍 Part 3: 데이터 조작과 필터링

Series 인덱스

# 인덱스 설정
print(sr.index)

# 데이터 값 배열
print(sr.values)

DataFrame 인덱스/열 이름 설정

# 배열로 DataFrame 생성 시 행 인덱스/열 이름 설정
data_array = [[90, 98, 85], [80, 89, 95], [70, 95, 100]]
df_custom = pd.DataFrame(data_array,
                        index=['가나다', '라마바', '사아자'],
                        columns=['수학', '과학', '음악'])
display(df_custom)
# 이름을 인덱스로 설정
df_indexed = df.set_index('이름')

# 인덱스 순서 재배열
df_reindexed = df_indexed.reindex(['라마바', '가나다', '사아자'])

# 인덱스 기준 정렬
df_sorted = df_indexed.sort_index()
display(df_sorted)

데이터 수정과 추가

행/열 추가

# 새로운 행 추가
# index가 겹치면 덮어씀, 새 index를 꼭 지정해야 함
df_data.loc[3] = ["차카타", 23, "여", 88] 
display(df_data)
# 새로운 열 추가
df_data['과학'] = [85, 90, 88, 91]
display(df_data)

행/열 삭제

삭제한 값을 보여주기만 한거고 실제로 없어진 것은 아님
drop() 안에서 마지막 inplace=True 붙여줘야함

inplace=True -> 원본 DataFrame을 직접 바꿀지 말지 결정하는 옵션

# 행 삭제
df_data_row = df_data.drop(3, axis=0)  # 또는 axis="index"
display(df_data_row)
# 또는 axis 쓰지 않아도 같은 결과 나옴, 'axis=0'이 기본값
df_data_row2 = df_data.drop(3)
display(df_data_row2)

# 열 삭제(열 삭제에서는 axis 쓰지 않으면 오류남)
df_data_col = df_data.drop('과학', axis=1)  # 또는 axis="columns"
display(df_data_col)

# 여러 행/열 동시 삭제
df_multi_drop = df_data.drop([0, 1], axis=0)  # 0, 1번 행 삭제
display(df_multi_drop)

df_multi_col_drop = df_data.drop(['나이', '성별'], axis=1)  # 여러 열 삭제
display(df_multi_col_drop)
# 조건으로 행 삭제 drop보다 이 방식이 많이 쓰인다함
# 나이 19살 삭제 -> 해당 코드는 19살이 아닌 것만 남겨달라는 의미
# 판다스 조건은 True인 행만 남기기 때문!
df_data_condition = df_data[df_data["나이"] != 19]
display(df_data_condition)

행, 열의 위치 바꾸기

# 전치 (transpose) : 행과 열의 위치를 바꾸는 함수
df_transposed = df.transpose()
# 또는 아래처럼 위치 바꿀 수 있음 ()가 안들어감 의미는 완전 같음
df_transposed = df.T

print(df_transposed)

📈 Part 4: 필터링 기법

불린 인덱싱

# 조건에 맞는 행 필터링
high_science = df_data[df_data['과학'] >= 80]
display(high_science)

# 복합 조건
high_both = df_data[(df_data['과학'] >= 80) & (df_data['점수'] >= 90)]
display(high_both)

query() 메소드 활용

조건 전체를 문자열 안에 쓰는 문법이고 조건을 만족하는 행만 DataFrame으로 반환

# query 메소드 사용
result1 = df_data.query('과학 >= 90')
display(result1)

# 복합 조건
result2 = df_data.query('과학 >= 80 and 점수 >= 90')
display(result2)

# 변수 사용
haha_score = 90
result3 = df_data.query('과학 >= @haha_score')
display(result3)

isin() 메소드 활용

여러 값 중 하나라도 포함되면 선택 할 때 쓰는 함수이며 isin( [리스트] )가 들어가야함

# 특정 값들 중 하나라도 포함되는 행 선택
selected_students = df_data[df_data['이름'].isin(['가나다', '사아자'])]
display(selected_students)

# 여러 점수 범위 선택
target_scores = [80, 90, 100]
high_scores = df_data[df_data['과학'].isin(target_scores)]
display(high_scores)

📝 Part 5: 텍스트 처리

텍스트 저장

# StringDtype 사용
fruits = pd.Series(["Apple", "Banana", "Cherry", "Date"], dtype="string")
print(fruits)

문자열 메소드

< 범위 지정하기 - 하이튼(-) 사용법 >

  • [a-c] → [abc]와 동일 (a부터 c까지)
  • [0-5] → [012345]와 동일 (0부터 5까지)
  • [A-Z] → 모든 대문자 알파벳 (A부터 Z까지)

< 자주 사용하는 범위 표현 >

  • [a-zA-Z] : 모든 알파벳 (소문자와 대문자 모두)
  • [0-9] : 모든 숫자 (0부터 9까지)
  • [가-힣] : 모든 한글 (가부터 힣까지)

< 제외하기 - ^ 사용법 >

  • [^0-9] → 숫자가 아닌 모든 문자 (알파벳, 특수문자, 공백 등)
  • [^abc] → a, b, c가 아닌 모든 문자
  • [^A-Z] → 대문자가 아닌 모든 문자

< 자주 사용하는 문자 클래스 >

[0-9] 또는 [a-zA-Z] 등은 무척 자주 사용하는 정규 표현식이다. 이렇게 자주 사용하는 정규식은 별도의 표기법으로 표현할 수 있다.

  • \d - 숫자와 매치된다. [0-9]와 동일한 표현식이다.
  • \D - 숫자가 아닌 것과 매치된다. [^0-9]와 동일한 표현식이다.
  • \s - 화이트스페이스(whitespace) 문자와 매치된다. [ \t\n\r\f\v]와 동일한 표현식이다. 맨 앞의 빈칸은 공백 문자(space)를 의미한다.
  • \S - 화이트스페이스 문자가 아닌 것과 매치된다. [^ \t\n\r\f\v]와 동일한 표현식이다.
  • \w - 문자+숫자(alphanumeric)와 매치된다. [a-zA-Z0-9_]와 동일한 표현식이다.
  • \W - 문자+숫자(alphanumeric)가 아닌 문자와 매치된다. [^a-zA-Z0-9_]와 동일한 표현식이다.

대문자로 사용된 것은 소문자의 반대임을 추측할 수 있다.

# 소문자/대문자 변환
print(fruits.str.lower())
print(fruits.str.upper())

# 문자열 길이
print(fruits.str.len())

# 문자열 분할
names_with_space = pd.Series(["김_철수", "이_영희", "박_민준"])
print(names_with_space.str.split("_"))

# 정규표현식 활용
phone_numbers = pd.Series(["010-1234-5678", "011-9876-5432"])
clean_numbers = phone_numbers.str.replace("-", "", regex=False)
print(clean_numbers)

# 특정 문자 포함 여부
print(fruits.str.contains("a"))
print(fruits.str.contains("A"))

# 패턴 추출 (정규표현식)
emails = pd.Series(["user1@gmail.com", "user2@naver.com", "user3@daum.net"])
domains = emails.str.extract(r'@([a-zA-Z]+)')
print(domains)

결측치 처리

결측치가 있는 행과 열을 확인할 때 : 아래 두 함수는 동의어로 결과값 완전히 동일함

df_messy.isnull()
df_messy.isna()
# 각각의 결측치 개수 확인 방법
df_messy.isna().sum()

🚨 이슈 - 결측치를 평균값으로 채울 때 실수(float)로 나옴

처음에 나이가 실수(float)로 나옴

원래 NaN은 정수(int)에 존재할 수 없어서 판다스가 컬럼 전체를 float으로 바꿔버린다고 함

그래서 결측치가 있던 숫자 컬럼은 자동으로 float 타입이 된다고 함

정수(int)로 바꿀 수 있는 3가지 방법에 대해 알아봤음!

age_mean = df_messy["나이"].mean()
# 1. 마지막에 .astype(int) 해당 부분을 추가해주면 소수점 아래를 버리고 정수로 바꿔줌
df_messy["나이"] = df_messy["나이"].fillna(age_mean).astype(int)

# 2. round도 추가해주면 평균을 반올림해서 정수로 바꿔주는 방법
#df_messy["나이"] = df_messy["나이"].fillna(round(age_mean)).astype(int)

# 3. 해당 방법은 판다스 권장 방식으로 실무에서 많이 쓰인다고함 눈에 익혀두면 좋을 듯!
#df_messy["나이"] = df_messy["나이"].round().astype("Int64")

display(df_messy)

🔎 참고 - 평균값 vs 중앙값

  • 평균값(mean): 전부 더해서 개수로 나눈 값
  • 중앙값(median): 정렬했을 때 가운데에 있는 값
[70, 80, 90]

# 평균값 = (70 + 80 + 90) / 3 = 80
# 중앙값 = 80

🔎 참고 - 병합 타입

merged_df = pd.merge(students, scores, on="학번", how="inner")

on=기준이 들어가야 함
how="inner"가 없는 1번 답이랑 같은 결과값이 나옴 inner가 기본값

how 의미
inner 둘 다 있는 것만 (기본값)
left students 기준
right scores 기준
outer 전부

exis 의미: 어느 방향으로 평균을 낼지 정함

  • axis=0 (기본값): 열 기준 → 각 과목의 평균(수학 평균, 영어 평균)
  • axis=1: 행 기준 → 각 학생의 평균(학생별 평균점수)

💡 자주 사용하는 메소드 정리

데이터 확인

df.head()          # 처음 5행
df.tail()          # 마지막 5행
df.info()          # 데이터 정보
df.describe()      # 통계 요약
df.shape           # 행, 열 개수
df.columns         # 열 이름
df.index           # 인덱스

결측값 처리

df.isnull()        # 결측값 확인
df.dropna()        # 결측값 행 제거
df.fillna(값)      # 결측값 채우기

데이터 변환

df.astype(타입)     # 데이터 타입 변환
df.rename()        # 행/열 이름 변경
df.reset_index()   # 인덱스 재설정