Pandas는 Python에서 데이터를 쉽고 효율적으로 분석하고 처리하기 위해 사용하는 대표적인 데이터 분석 라이브러리다. 엑셀과 비슷한 형태의 표(Tabular Data)를 다룰 수 있으며, Series와 DataFrame이라는 강력한 자료구조를 제공한다. CSV, Excel, SQL 등의 다양한 데이터 파일을 불러와 정렬, 필터링, 그룹화, 결측치 처리, 통계 분석 등을 간편하게 수행할 수 있으며, 대량의 데이터를 빠르게 가공할 수 있다.
# 설치
pip install pandas
# 또는
python -m pip install pandas
Series
Series는 Pandas에서 사용하는 가장 기본적인 1차원 데이터 구조로, 값(Value)과 인덱스(Index)가 함께 구성된 자료형이다. NumPy의 1차원 배열과 비슷하지만, 각 데이터에 이름표 역할을 하는 인덱스가 추가되어 데이터를 더 체계적으로 관리할 수 있다.

idx = ['김사과', '반하나', '오렌지', '이메론', '배애리']
data = [67, 75, 90, 62, 98]
# pd.Series(데이터, 인덱스, ...)
pd.Series(data)
'''
0 67
1 75
2 90
3 62
4 98
dtype: int64
'''
ser1 = pd.Series(data, idx)
print(ser1)
print(ser1.index) # index 객체가 따로 생성
print(ser1.values) # values는 ndarray로 객체 생성
print(type(ser1.values))
'''
김사과 67
반하나 75
오렌지 90
이메론 62
배애리 98
dtype: int64
Index(['김사과', '반하나', '오렌지', '이메론', '배애리'], dtype='str')
[67 75 90 62 98]
<class 'numpy.ndarray'>
'''
Index 객체
Index 객체는 Series와 DataFrame에서 각 데이터의 위치를 식별하고 관리하기 위한 '이름표(Label)' 역할을 하는 자료구조이다. 일반 리스트처럼 보이지만 단순한 데이터 저장용이 아니라, 데이터를 빠르게 검색하고 정렬하며 서로 다른 데이터끼리 매칭할 수 있도록 최적화된 Pandas 전용 객체다.

DataFrame
데이터프레임(DataFrame)은 판다스(Pandas) 라이브러리에서 제공하는 중요하고 강력한 데이터 구조로, 2차원 테이블 형태 데이터를 다루는 데 사용된다. 또한 각 요소는 인덱스(Index), 열(Column), 값(Value)으로 구성되어 있다. 데이터프레임은 행과 열로 이루어져 있으며, 각 열은 다양한 데이터 타입을 가질 수 있다. 값은 넘파이의 ndarray기반으로 저장된다.

data = [[67, 93, 91],
[75, 68, 96],
[87, 81, 82],
[62, 70, 75],
[98, 56, 87]]
idx = ['김사과', '반하나', '오렌지', '이메론', '배애리']
col = ['국어', '영어', '수학']
# pd.DataFrame(데이터, 인덱스, 컬럼, ...)
pd.DataFrame(data)
'''
0 1 2
0 67 93 91
1 75 68 96
2 87 81 82
3 62 70 75
4 98 56 87
'''
pd.DataFrame(data, idx)
# idx값 추가
'''
0 1 2
김사과 67 93 91
반하나 75 68 96
오렌지 87 81 82
이메론 62 70 75
배애리 98 56 87
'''
pd.DataFrame(data, idx, col)
# column값 추가
'''
국어 영어 수학
김사과 67 93 91
반하나 75 68 96
오렌지 87 81 82
이메론 62 70 75
배애리 98 56 87
'''
CSV 파일 읽어오기
CSV 파일은 Comma-Separated Values (쉼표로 구분된 값) 파일의 약자로, 데이터를 단순한 텍스트 형식으로 저장하는데 사용되는 파일 형식이다.
# csv 파일 메모리에 올리기
df = pd.read_csv('csv 파일 주소')
type(df)
# pandas.DataFrame
# info(): 데이터를 파악 할 수 있는 메서드
df.info()
# 클래스 정보, entry 정보, 컬럼의 개수, 데이터타입, 메모리 사용량 등이 출력된다.
'''
<class 'pandas.DataFrame'>
RangeIndex: 20 entries, 0 to 19
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 이름 20 non-null str
1 소속사 20 non-null str
2 성별 20 non-null str
3 생년월일 20 non-null str
4 키 20 non-null str
5 혈액형 18 non-null str
6 브랜드평판지수 20 non-null str
dtypes: str(7)
memory usage: 1.2 KB
```
df.columns
# Index(['이름', '소속사', '성별', '생년월일', '키', '혈액형', '브랜드평판지수'], dtype='str')
# 컬럼의 값을 바꾸고 싶을때는 전체 컬럼을 만들어서 바꿔줘야 함.
new_columns = ['name', 'company', 'gender', 'birthday', 'height', 'blood', 'brand']
df.colums = new_columns
# describe() : 통계 정보를 반환, 기본값은 숫자정보를 중심으로 통계정보가 반환됨
df.describe()
# df.describe(include=str) > 숫자정보가 있을때 문자열들의 정보를 반환하고 싶을 때

# 원하는 개수의 데이터 보기
df.head() # 상위 5개의 row를 출력
df.head(n) # 상위 n개의 row를 출력
df.tail() # 하위 5개의 row를 출력
df.tail(n) # 하위 n개의 row를 출력
정렬하기
# index로 오름차순 정렬 : 기본값
df.sort_index()
# index로 내림차순 정렬
df.sort_index(ascending=False)
# 원하는 series로 오름차순 정렬
df.sort_values(by='column_name') # 실제 데이터값의 순서변경은 없고 정렬된 복사본만 출력됨
# 원하는 series로 내림차순 정렬
df.sort_values(by='column_name', ascending=False)
# 문자열 비교는 ASCII기준으로 사전 순서처럼 비교하기 때문에 '182cm' > '182.2cm'처럼 이상한 결과가 나올 수 있음.
# 함수형 프로그래밍 (Chaining)
# height에서 cm를 제거하고 문자열을 실수형으로 바꾸는 과정
df['height'] = df['height'].str.replace('cm','').astype(float)
# 1차 정렬 : 키(내림차순), 2차 정렬 : 브랜드(내림차순)
df.sort_values(by=['height', 'brand'], ascending=[False, False], na_position='first')
# na_position: 값이 NaN이면 제일 아래 깔리는게 default, 그걸 위로 올리고 싶을때는 first
# describe(): 데이터프레임의 기술 통계량을 요약해주는 함수

인덱싱
# 한개의 컬럼(series)만 확인하기
df['column_name']
# df.column_name 으로도 확인이 가능하지만 컬럼명이 한글이거나, 공백이 있거나,
# 특수문자가 있으면 사용할 수 없으므로 위의 방식이 비교적 안전함.
# 슬라이싱을 통해 데이터 확인 가능
df[:3]
# loc 인덱싱
df.loc[:, 'name'] # loc[행에대한 기준, 열에대한 기준]
'''
loc는 DataFrame이나 Series에서 인덱스 이름(Label)과 컬럼명을 기준으로 데이터를 선택하는 인덱서(Indexer)
즉, 숫자 위치가 아니라 실제 행 이름과 열 이름을 사용하여 데이터를 가져온다.
loc는 사람이 읽기 쉽고 데이터 의미를 기준으로 접근하기 때문에 데이터 분석과 전처리 작업에서 가장 많이 사용된다.
'''
df.loc[2:5, 'name'] # 5번을 포함 (2부터 5까지)
df.loc[2:5, ['name', 'gender', 'height']] # 여러개를 넣을 땐 리스트로 묶어서 사용
df.loc[[2, 5], ['name', 'gender', 'height']] # 2번, 5번에 해당하는 각각의 정보를 가져올 때
df.loc[2:5, 'name':'gender'] # 2부터 5까지 + name부터 gender까지
# iloc 인덱싱 : index로 인덱싱
df.iloc[:, 0] # 첫번째 열의 모든 데이터를 가져옴
'''
iloc는 DataFrame이나 Series에서 숫자 위치(Index Position)를 기준으로 데이터를 선택하는 인덱서(Indexer)
즉, 실제 인덱스 이름이 아니라 행과 열의 순서 번호를 사용하여 데이터를 가져온다.
iloc는 데이터의 실제 이름과 상관없이 순서 기준으로 접근하기 때문에 반복처리나 특정 위치 데이터를 다룰 때 많이 사용.
'''
df.iloc[:, 0:3] # 3을 포함하지 않음 (0부터 2까지)
df.iloc[:, [0,3]] # 0과 3열
df.iloc[1:5, 0:2] # 1부터 4행, 0부터 1열
df['height'] >= 180 # 180 이상인 bool 값 출력
df[df['height'] >= 180] # 180 이상인 데이터값 출력
df[df['height'] >= 180]['name'] # 180 이상인 데이터의 이름만 출력
df[df['height'] >= 180][['name', 'gender', 'height']]
df.loc[df['height'] >= 180, ['name', 'gender', 'height]] # 위의 코드와 동일한 결과 출력
blood = ['A', 'B']
df['blood'].isin(blood) # 해당 값이 데이터 안에 있는지 bool로 출력
df[df['blood'].isin(blood)] # True에 해당하는 데이터 출력
df.loc[df['blood'].isin(blood), :]
결측값
결측값(Missing Value)은 데이터가 존재하지 않거나 비어있는 값을 의미하며, 보통 NaN(Not a Number) 형태로 표현된다. 예를 들어 키, 몸무게, 나이 같은 정보가 입력되지 않았거나 수집 과정에서 누락된 경우 결측값이 발생한다. 데이터 분석과 머신러닝에서는 결측값이 매우 중요하며, 그대로 두면 평균 계산, 통계 분석, 모델 학습 과정에서 오류나 성능 저하가 발생할 수 있다. 결측값 처리는 데이터 품질을 높이고 정확한 분석 결과를 얻기 위한 매우 중요한 데이터 전처리 과정이다.
df.isna() # True 부분이 결측치
df.isnull() # True 부분이 결측치
df.notna() # False 부분이 결측치
df.notnull() # False 부분이 결측치
df['column_name'].isnull() # series별로 조회 가능
import numpy as np
df.loc[[8,19], 'height'] = np.nan # 8,19행의 height를 NaN으로 변경
df[df['height'].notnull()] # 결측치가 있는 행을 제외한 데이터 조회
df[~df['height'].isnull()] # ~ : 반대 결과 > notnull()
# 데이터프레임 복사
df_copy = df.copy()
# fillna() : 결측값을 채워주는 함수
df_copy['height'].fillna(0) # 결측값을 0으로 채움
# mean() : 데이터의 평균
height = df_copy['height'].mean()
df_copy['height'] = df_copy['height'].fillna(height) # 결측값을 데이터의 평균값으로 채움
# median() : 50%값, 중위값
height = df_copy['height'].median()
df_copy['height'] = df_copy['height'].fillna(height) # 결측값을 데이터의 중앙값으로 채움
평균과 중앙값
평균(Mean)은 데이터의 모든 값을 더한 뒤 데이터 개수로 나눈 값으로, 전체 데이터의 중심적인 경향을 나타내는 대표적인 통계값이다. 예를 들어 [10, 20, 30]의 평균은 (10+20+30)/3 = 20이다.
반면 중앙값(Median)은 데이터를 크기순으로 정렬했을 때 가장 가운데 위치한 값을 의미하며, 이상치(Outlier)의 영향을 적게 받는 특징이 있다. 예를 들어 [10, 20, 30]의 중앙값은 20이다.
만약 데이터 개수가 짝수라 가운데 값이 두 개라면, 두 가운데 값의 평균을 중앙값으로 사용한다.
예를 들어 [10, 20, 30, 40]에서는 가운데 값인 20과 30의 평균인 25가 중앙값이 된다.
즉, 평균은 전체 데이터를 모두 반영하는 대표값이고, 중앙값은 데이터의 정중앙 위치를 나타내는 대표값이라 할 수 있다.
# dropna() : 결측값이 있는 행 또는 열을 제거. 결측값이 한개라도 있는 경우 삭제
# axis=0 (행 삭제), axis=1 (열 삭제)
df_copy.dropna() # 결측값이 있는 행 삭제, df_copy.dropna(axis=0)과 동일
df_copy.dropna(axis=1) # 결측값이 있는 열 삭제
행, 열 추가 및 삭제
# 추가하고자 하는 데이터를 먼저 딕셔너리형태로 생성
dic = {
'name': '김사과',
'company': '애플',
'gender': '여자',
'birthday': '2000-01-01',
'height': 160.0,
'blood': 'A',
'brand': 1234567
}
df_copy.loc[len(df_copy)] = dic # 맨 뒤 행으로 데이터 추가
df_copy['nation'] = '대한민국' # 새로운 열 추가
df_copy.loc[df_copy['name'] == '김사과', 'nation'] = '미국'
# 이름이 '김사과'에 해당하는 데이터 행의 'nation' 데이터 값을 '미국'으로 변경
# 행 제거하기
df_copy.drop(20, axis=0) # 20행 데이터 제거
# 여러 행 제거
df_copy.drop([1, 3, 5, 7, 20], axis=0)
# 열 제거하기
df_copy.drop('nation', axis=1) # 'nation'열 제거
# 여러 열 제거
df_copy.drop(['nation', 'company'], axis = 1)
통계 함수
print(df_copy['height'].sum()) # 합계
print(df_copy['height'].count()) # 개수, NaN은 포함하지 않음
print(df_copy['height'].mean()) # 평균
print(df_copy['height'].median()) # 중앙값
print(df_copy['height'].max()) # 최대값
print(df_copy['height'].min()) # 최소값
print(df_copy['height'].var()) # 분산
print(df_copy['height'].std()) # 표준편차
분산과 표준편차
분산(Variance)은 데이터가 평균으로부터 얼마나 퍼져 있는지를 수치로 나타낸 값으로, 각 데이터와 평균의 차이를 제곱한 뒤 평균을 내어 계산한다. 값이 클수록 데이터의 흩어짐이 크다는 의미이며, 값이 작을수록 데이터가 평균 근처에 모여 있다는 뜻이다. 하지만 분산은 차이를 제곱해서 계산하기 때문에 단위가 원래 데이터와 달라진다는 단점이 있다. 이를 해결하기 위해 사용하는 것이 표준편차(Standard Deviation)이며, 표준편차는 분산에 제곱근(sqrt)을 취한 값이다. 따라서 원래 데이터와 같은 단위를 사용하므로 해석이 더 직관적이다.

# groupby(): 데이터를 그룹으로 묶어 분석할 때 사용
df_copy.groupby('blood')
# 그룹을 맺으면 통계함수를 사용 할 수 있다.
df_copy.groupby('blood').count()
df_copy.groupby('blood').mean(numeric_only=True) # numeric_only = 수치화 할 수 있는 자료만
# drop_duplicates() : 중복된 데이터를 제거, keep='first'가 기본값
df_copy['blood'].drop_duplicates(keep='last')
df_copy['blood'].drop_duplicates() # 데이터 내에서 Unique한 값을 찾을 때 좋음
# value_counts() : 열의 각 값에 대한 데이터의 개수를 반환, 기본은 NaN을 생략
df_copy['blood'].value_counts(dropna=False) # NaN을 포함
데이터프레임 다루기
# concat(): 여러개의 데이터프레임이나 시리즈를 하나로 합칠 때 사용하는 함수
pd.concat([df1, df1_copy]) # axis=0 (기본값) # 데이터가 위아래로 붙는다.
# reset_index(): index를 새롭게 적용
# drop=True 옵션을 사용하여 기존 index가 컬럼으로 만들어지는 것을 방지
df1_concat = pd.concat([df1, df1_copy])
df1_concat.reset_index(drop=True)
pd.concat([df1_copy, df2_copy], axis=1) # 같은 index와 옆으로 결합
# merge(): 특정 고유한 키(unique, id)값을 기준으로 합침
# merge(데이터프레임1, 데이터프레임2, on='유니크값', how='병합의 기준')
# 병합의 기준: left(왼쪽 기준), right(오른쪽 기준), inner(교집합), cross(key별로 모두 합침)
날짜타입
컴퓨터가 날짜 계산을 쉽게 할 수 있도록 만들어진 전용 타입.
# to_datetime(): str타입에서 datetime타입으로 변환
df1_copy['생년월일'] = pd.to_datetime(df1_copy['생년월일'])
print(type(df1_copy['생년월일']))
print(df1_copy['생년월일'].dtypes)
# <class 'pandas.Series'>
# datetime64[us]
df1_copy['생년월일'].dt.year # 년 정보만 가져옴
df1_copy['생년월일'].dt.month # 월 정보만 가져옴
df1_copy['생년월일'].dt.day # 일 정보만 가져옴
df1_copy['생년월일'].dt.hour # 시간 정보만 가져옴
df1_copy['생년월일'].dt.minute # 분 정보만 가져옴
df1_copy['생년월일'].dt.second # 초 정보만 가져옴
df1_copy['생년월일'].dt.dayofweek # 요일 정보를 0~6으로 매핑
df1_copy.select_dtypes(include='str') # 문자열 칼럼만 가져오기
df1_copy.select_dtypes(exclude='str') # 문자열 칼럼만 빼고 가져오기
# 문자열을 가지고 있는 칼럼의 이름만 변수에 저장하여 출력
str_cols = df1_copy.select_dtypes(include='str').columns
str_cols
# Index(['이름', '소속사', '성별', '키', '혈액형', '브랜드평판지수'], dtype='str')
자주 사용하는 함수
1. apply()
Pandas의 apply() 함수는 데이터프레임이나 시리즈의 데이터를 사용자 정의 함수 또는 내장 함수에 적용하여 새로운 값을 계산하거나 변환할 때 사용된다. 데이터를 행(row) 또는 열(column) 단위로 처리할 수 있는 강력한 도구이다.
# '남'은 1로 '여'는 0으로 표기하도록 설정
def male_or_female(x):
if x == '남':
return 1
elif x == '여':
return 0
else:
return None
df1_copy['성별'].apply(male_or_female)
# lambda함수를 통해 위의 코드를 한줄로 표현
df1_copy['성별'].apply(lambda x: 1 if x == '남' else 0)
df1_copy['성별2'] = df1_copy['성별'].apply(lambda x: 1 if x == '남' else 0) # 파생 변수
2. map()
Pandas의 map() 함수는 Series 객체에서 사용할 수 있는 함수로, 각 요소에 대해 함수나 매핑 규칙을 적용하여 새로운 값을 계산하거나 변환할 때 사용된다. map()은 데이터의 각 요소를 순회하며 특정 작업을 수행하므로, 데이터를 가공하거나 변환하는데 유용.
map_gender = {'남':1, '여':0}
df1_copy['성별'].map(map_gender)
df1_copy['성별3'] = df1_copy['성별'].map(map_gender)
df1_copy.head()
데이터프레임의 산술연산
df1 = pd.DataFrame({
'파이썬':[60, 70, 80, 90, 95],
'데이터분석':[40, 60, 70, 55, 87],
'머신러닝딥러닝':[35, 40, 30, 70, 55]
})
df2 = pd.DataFrame({
'파이썬':['C', 'B', 'B', 'A', 'A'],
'데이터분석':[40, 60, 70, 55, 87],
'머신러닝딥러닝':[35, 40, 30, 70, 55]
})
df1['파이썬'] + df1['데이터분석'] + df1['머신러닝딥러닝']
# df1에 총점, 평균이라는 파생변수를 만들고 파생변수에 총점, 평균을 구해서 저장
df1['총점'] = df1['파이썬'] + df1['데이터분석'] + df1['머신러닝딥러닝']
df1['평균'] = df1['총점'] / 3
print(df1['파이썬'].sum()) # df1['파이썬'].sum(axis=0)
print(df1['파이썬'].mean())
# df1 + df2 # TypeError: unsupported operand type(s) for +: 'int' and 'str'
df1 + 10
# df2 + 10 # TypeError: can only concatenate str (not "int") to str
df1 = pd.DataFrame({
'데이터분석':[40, 60, 70, 55, 87],
'머신러닝딥러닝':[35, 40, 30, 70, 55]
})
df2 = pd.DataFrame({
'데이터분석':[40, 60, 70, 55],
'머신러닝딥러닝':[35, 40, 30, 70]
})
df1 + df2 # 행의 갯수가 다를 경우 빠진 데이터를 NaN으로 취급하기 때문에 결과는 NaN
get_dummies
머신러닝 모델은 숫자 데이터는 처리할 수 있지만 문자열 데이터는 직접 처리할 수 없다. 따라서 문자열로 이루어진 범주형 데이터를 숫자 형태로 변환하는 과정이 필요하며, 이때 많이 사용하는 방법이 원-핫 인코딩(One-Hot Encoding)이다. Pandas에서는 pd.get_dummies() 함수를 이용해 쉽게 원-핫 인코딩을 수행할 수 있다.
원-핫 인코딩
범주형 데이터를 각 항목별로 새로운 컬럼으로 분리하고, 해당 값이면 1, 아니면 0을 부여하는 방법. 원-핫 인코딩은 범주형 데이터(Categorical Data)에 적용한다. 범주형 데이터란 데이터의 값이 크기나 순서를 의미하는 것이 아니라 단순히 종류(Category)를 나타내는 데이터를 말한다.

👉 문자열이라고 무조건 원-핫 인코딩하는 것은 아니다. 예를 들어 Pandas에서 문자열(str) 타입으로 인식할 수 있지만, 생년월일은 시간의 흐름이라는 의미가 있다. 따라서 단순히 문자열이라는 이유만으로 원-핫 인코딩을하면 안된다.
반대로 숫자형 데이터라고 해서 항상 수치형 데이터는 아니다. 동급 또는 학년 같은 값은 실제 숫자라기보다는 특정 범주를 나타내는 경우가 많다. 이 경우에는 원-핫 인코딩을 고려할 수 있다.
blood_map = {'A':0, 'B':1, 'AB':2, 'O':3}
df1_copy['blood_code'] = df1_copy['blood'].map(blood_map) # 라벨인코딩
pd.get_dummies(df1_copy['blood'])
'''
A AB B O
0 False False True False > B형
1 True False False False > A형
'''
df1_copy = pd.get_dummies(df1_copy, columns=['blood'])'데이터 분석' 카테고리의 다른 글
| 웹 크롤링 (Crawling) (0) | 2026.06.28 |
|---|---|
| 넘파이 (NumPy) (0) | 2026.06.01 |