2026. 5. 31. 00:16ㆍAI/Python

판다스(Pandas)는 Python에서 데이터를 쉽고 효율적으로 분석하고 처리하기 위해
사용하는 대표적인 데이터 분석 라이브러리이다. 엑셀과 비슷한 형태의 표(Tabular Data)를
다룰 수 있으며, Series와 DataFrame 이라는 강력한 자료구조를 제공한다.
CSV, Excel, SQL 등의 다양한 데이터 파일을 불러와 정렬, 필터링, 결측치 처리, 통계 분석 등을
간편하게 수행할 수 있으며, 대량의 데이터를 빠르게 가공할 수 있다.
설치방법
pip install pandas
python -m pip install pandas
첫번째 방법은 글로벌 설치이고, 두번째는 가상 환경 내의 설치이다.
또한 외부 라이브러리이므로, 사용하려면 import 문으로 라이브러리를 불러와야 한다.
import pandas as pd
시리즈 (Series)
판다스(Pandas)에서 제공하는 가장 기본적인 1차원 데이터 구조이며
값(Value)과 인덱스(Index)가 함께 구성된 자료형이다.
넘파이(Numpy)의 1차원 배열과 비슷하나, 각 데이터에 이름표 역할을 하는 인덱스가 추가되어
데이터를 더 체계적으로 관리할 수 있다.

데이터의 값(Value)이 들어가면 그 값에 이름표 역할을 하는 인덱스(Index)가 부여되는 형식이다.
예시를 살펴보면서 이해해보자.
idx = ['김사과','반하나','오렌지','이메론','배애리']
data = [67, 75, 90, 62, 98]
여기서 시리즈를 만들려면, 판다스(Pandas) 안의 함수(메소드)를 통해 만드는데 Series 메소드를 통해 만든다.
pd.Series(data,idx)
첫 매개변수에 데이터 값(Value)를 넣고, 두번째 매개변수에 인덱스(Index) 항목을 넣는 것이다.
그런데 만약 다음처럼 인덱스(Index)를 안 넣을 경우에는
pd.Series(data)

다음과 같은 결과가 나오며, 자동으로 번호(인덱스)가 추가되는 방식이다.
반대로 인덱스를 넣을 경우에는 번호(인덱스)에 idx가 차례대로 배치되는 모습이다.

이제 이 시리즈를 변수에 저장해보자.
ser1 = pd.Series(data, idx)
그리고 시리즈의 속성들을 출력해보자. 먼저 index를 출력해보자.
print(ser1.index)

출력해보니까 Index 객체라고 정의되어 있다.
이번에는 Series의 values를 출력해보자.
print(ser1.values)

시리즈는 이렇게 인덱스 객체와 다차원 배열(ndarray)인 values로 구성되어 있다는 점을 알았다.
그런데 인덱스 객체는 처음 보는 객체여서 설명이 필요할 것 같다.
여기서 인덱스(Index) 객체는 시리즈(Series)와 데이터 프레임(DataFrame)에서
각 위치를 식별하고 관리하기 위한 이름표(Label) 역할을 하는 자료구조이다.
일반 리스트처럼 보이지만, 단순한 데이터 저장용이 아니라 데이터를 빠르게 검색하고 정렬하며
서로 다른 데이터끼리 매칭할 수 있도록 최적화된 판다스(Pandas) 전용 객체이다.
리스트와 달리, 데이터를 인덱싱으로 접근해서 수정하지 못한다.
데이터 프레임(DataFrame)
판다스(Pandas) 라이브러리에서 제공하는 중요하고 강력한 데이터 구조로,
2차원 테이블 형태의 데이터를 다루는데 사용된다. 각 요소는 인덱스(Index), 열(Column), 값(Value)으로 구성되어 있다.
데이터 프레임은 행과 열로 이루어져 있고, 각 열은 다양한 데이터 타입을 가질 수 있다. 값은 ndarray 기반으로 저장된다.

그림에서 보면 알수 있듯이 시리즈(Series)가 모이면, 데이터 프레임(DataFrame)이 되는 구조이다.
다만 합칠 때는 시리즈(Series)의 인덱스가 같아야 합쳐진다. 합쳐진 각 시리즈의 제목이 한 열(컬럼)이 되는 것이다.
직접 예제를 살펴보자.
data = [[67, 93, 91],
[75, 68, 96],
[87, 81, 82],
[62, 70, 75],
[98, 56, 87]]
idx = ['김사과', '반하나', '오렌지', '이메론', '배애리']
col = ['국어', '영어', '수학']
다음은 데이터, 인덱스, 컬럼 변수들이다. 이걸 가지고 데이터프레임을 한번 만들어보자.
일단 데이터 값만 먼저 넣을 것이다.
pd.DataFrame(data)

그 결과는 들어가지 않은 인덱스와 컬럼은 0,1,2,.... 같은 형태로 채워지고
2차원 데이터(값)이 그에 맞게 구성되는 것을 확인할 수 있다.
이번에는 인덱스까지 넣어보자.
pd.DataFrame(data, idx)

그러면 왼쪽 행에 각각 인덱스가 표시되고, 넣지 않은 컬럼만 0,1,2... 같이 자동 배치될 것이다.
그러면 컬럼까지 전부 다 넣어보자.
(참고로 매개변수의 순서를 맞추지 않으면 당연히 오류가 난다.)
pd.DataFrame(data, idx, col)

이제 완벽한 데이터 프레임이 구성이 되었다.
그런데 앞선 내용에서 설명을 하긴 했지만, 매개변수가 몇십개가 넘어가는 메소드나 함수에서 다 순서를 맞출 수 있을까?
아니다. 매개변수를 미리 셋팅해놓는 기본 매개변수로 해결할 수 있을 것이다.
그런데 만약 진짜 넘겨야 되는 매개변수라면 어떻게 해야 할까? 전부 순서를 맞춰 주는 것이 아니라,
매개변수 = 값 형태로 매개변수로 넣어주면 되는 것이다.
그래서 위와 같은 경우도, 전부 순서를 맞출 필요 없이 매개변수 = 값 형태로 구성할 수 있다. 다음처럼 말이다.
df = pd.DataFrame(index=idx,columns=col,data=data)
즉 매개변수의 이름을 쓰면 순서가 달라도 관계없기 때문에 매개변수의 순서를 일일이 확인할 필요가 없는 것이다.
앞서 Python의 기본에서 학습했던 내용이므로, 꼭 기억해두자.
그러면 저장한 데이터프레임 변수에서 각 속성들을 출력해보자.
print(df.index)
print(df.columns)
print(df.values)

그런데 여기서 columns 속성이 인덱스 객체로 인식되는 점에 주목해보자.
만약 데이터가 기하급수적으로 많을 경우, 값은 다차원 데이터이므로 컬럼을 통해서 데이터 값을 찾아야 할 수도 있다
즉 인덱스와 컬럼 항목은 어떤 데이터를 찾는데 속도가 빨라야 하므로 인덱스 객체를 이용하는 것이다.
이번에는 딕셔너리를 사용해서 데이터프레임을 만들어보자.
dic = {
'국어':[67, 75, 76, 62, 98],
'영어':[93, 68, 81, 70, 56],
'수학':[91, 96, 82, 75, 87]
}
df = pd.DataFrame(data=dic, index=idx)
키와 값의 구조가 데이터에 들어가게 되면, 자동으로 컬럼까지 만들어주는 특징이 있다.
인덱스는 그대로 구조대로 가고, 딕셔너리의 키가 컬럼이 되고, 딕셔너리의 값은 값이 되는 특징이다.
데이터 프레임 만들기가 편한 구조로, 딕셔너리를 이용하기도 한다.

CSV 파일 읽기
CSV 파일이란 Comma-Separated Values 파일의 약자로,
데이터를 단순한 텍스트 형식으로 저장하는데 사용하는 파일 형식이다.
직접 CSV 파일 하나를 읽어오겠다. 동시에 이 파일을 메모리에 올리겠다.
메모리에 올리면서 동시에 csv 파일을 데이터프레임으로 만들 것이다.
df = pd.read_csv("./광고모델_브랜드평판.csv")

실제로 타입을 찍어보면 다음과 같다.
print(type(df))

이제 판다스(Pandas)의 데이터프레임을 만들고 나서 데이터 분석 위해 해야 할 것들 몇가지를 알아보자.
먼저, 데이터프레임(DataFrame)의 info라는 메소드이다.
df.info()
이 메소드를 실행하면 다음과 같은 분석 결과가 나온다.

가장 중요한 이 분석 결과를 볼 줄 알아야 하는데,
가장 위에 있는 것은 csv 파일을 불러와 저장한 변수의 데이터 타입인데, 여기서는 데이터프레임 이다.
두번째에 있는 RangeIndex는 전체 데이터 갯수 및 인덱스 범위를 지칭한다. 여기서는 총 20개로 지칭된다. (0 ~ 19)
세번째에 있는 Data Columns는 말 그대로 데이터 컬럼의 개수이다. 여기서는 밑에 나오듯이 총 7개이다.
그 컬럼 중에 중요한 것이 Non-Null Count 항목이다. 말 그대로, Null이 아닌 데이터 개수를 지칭한다.
아까 NaN이 있었던 혈액형을 제외하고 다 채워져 모두 20개로 지칭되고 있다. 혈액형만 2개의 결측값이 있어 18개이다.
마지막으로, DType은 데이터 타입을 의미한다. 여기선 컬럼의 각 요소의 데이터 타입을 말하는데 object로 표시된다.
(자세히 들어가면 str으로 이해해도 된다.)
전체적으로 이 메소드의 목적은 Null 값이 몇개 있는지 한 눈에 보기 위함이다.
Null 값이 중요한 이유는 나중에 머신러닝/딥러닝에서 데이터 학습을 시킬 때
Null 값이 있으면 위험하기 때문이다. (잘못된 연산 발생)
이번에는 df (데이터프레임 변수)의 컬럼 부분을 출력해보겠다.
df.columns
아까 살펴봤듯이 데이터프레임이므로, 컬럼 부분은 인덱스 객체로 출력될 것이다.

여기서 컬럼 이름을 바꿔주는 방법을 알아보자.
new_columns = ['name','company','gender','birthday','height','blood','brand']
df.columns = new_columns
df
컬럼의 개수와 동일한 문자열이 들어간 새로운 리스트를 만든 후에 column 속성에 한꺼번에 할당하는 방법이다.

실제로 컬럼이 바뀐 문자열로 변경됨을 확인할 수 있다!
이번에는 통계 정보를 보는 방법을 알아보자.
df.describe()
그 결과는 다음과 같다.

저 메소드에서는 수치 값으로 볼 수 있는 통계 정보가 나오는데, 그러는데 사실 데이터 값 중에
수치값으로 볼 수 있는게 없다는 것을 알 것이다. (데이터 타입이 모두 str 이기 때문에)
그래서 위는 문자열에 관련된 통계 정보만 나온 것이다.
그래서 먼저 키(height)에 해당하는 height 값을 숫자형으로 바꿔줘야 할 것 같다.
그래야 다음과 같은 describe 정보를 얻을 수 있기 때문이다.

하지만, 지금은 다음과 같이 문자에 관련 정보만 나오고 있는 것이다.
df.describe(include=object)

이제, 숫자에 관한 정보를 보기 전에 먼저 원하는 갯수의 데이터 보는 방법부터 알아보자.
1. 상위 5개의 row(행) 출력
총 데이터에서 위에서부터 5개의 행만 출력하는 메소드이다.
아무것도 설정하지 않을 경우 기본 값이 5이기 때문에 상위 5개만 출력된다.
df.head()

만약, 상위 3개만 보고 싶다면, 다음과 같이 설정해주면 된다.
df.head(3)

2. 하위 5개의 row(행) 출력
만약에 내가 끝에서부터 데이터를 가져오고 싶다면, 다음과 같이 설정하자.
이것 역시 기본 값은 5이므로, 하위 5개의 행이 출력된다.
df.tail()

데이터 정렬
오름차순 정렬
index 기준으로 오름차순 정렬하는 메소드이다.
그러나 이미 처음부터 index 기준으로 정렬이 되어 있는 형태이다.
그러므로 변화는 없을 것이다.
df.sort_index()

내림차순 정렬
index를 기준으로 내림차순 정렬하는 메소드이다.
df.sort_index(ascending=False)

정렬 기준 바꾸기
키(height)를 기준으로 오름차순 정렬을 하려고 한다. 다음과 같은 설정을 추가해주면 된다.
df.sort_values(by="height")

이렇게 정렬이 잘 된 것처럼 보이지만, 여기에서는 문제가 하나 발생한다.
문자열 비교는 사전(Dictionary) 순서로 비교하므로 182cm > 182.2cm 같은 이상한 결과가 발생하기 때문이다.
다음처럼 말이다.

그 원리는 182cm와 182.2cm를 비교하게 되면, 182 부분까지는 똑같고
c와 .이랑 비교를 하게 되므로, .의 유니코드가 우선이라 오름차순 순위가 올라가는 사태가 발생하는 것이다.
그래서 인덱스가 height인 시리즈를 선택해서 그 값들을 숫자로 바꾸는 과정이 지금 필요하다.
다음과 같이 말이다.
df['height'] = df['height'].str.replace('cm','').astype(float)
문자열 중 cm을 제거하고 숫자만 남은 문자열을 숫자형으로 캐스팅하는 코드이다.
그러면 height 시리즈는 다음과 같이 실수형으로 변화한다.

이렇게 바꾸었으면 height 컬럼은 숫자로써 사용할 수 있겠다.
그런 김에 숫자처럼 생긴 문자열은 brand 시리즈도 숫자형으로 캐스팅해보자.
df['brand'] = df['brand'].str.replace(",", '').astype(int)
그럼 다음과 같은 결과가 나온다.

그러면 데이터 타입이 제대로 바뀌었는지 info 메소드를 통해 정보를 확인해보자.
df.info()

결과를 보면 알겠지만, height은 float 타입으로, brand는 int 타입으로 변경되었다.
그러면 통계 정보도 바뀌지 않았을까? 확인해보자.
df.describe()

이제 숫자 위주의 통계 정보가 보임을 확인할 수 있다!
그러면 키(height)으로 다시 내림차순 정렬해보자.
df.sort_values(by='height', ascending=False)

아까 문제였던 182.2cm와 182cm의 비교가 해결됨을 확인할 수 있다.
이제 한 가지 해결할 문제가 생기는데

이렇게 키가 똑같은 중복 데이터가 생겼을 때는 추가로 정렬 기준이 있을까라는 의문이 생긴다.
그 때 키가 같을 때는 브랜드 평판지수(brand)가 높은 사람이 상단으로 올라가게 하고 싶다는 것이다.
그러기 위해서 2차 정렬 셋팅이 필요하다.
# 1차 정렬: 키 (내림차순) 2차 정렬: 브랜드 (내림차순)
df.sort_values(by=["height","brand"], ascending=[False, False], na_position='first')

이렇게 같은 키일때는 brand 크기 순서대로 내림차순됨을 확인할 수 있다.
여기서 na_position 옵션은 기준 값이 NaN이 되었을 때 어디에 위치시킬지 정하는 옵션이다.
지금 같은 경우, first이므로, 맨 위에 위치하는 경우이다.
인덱싱
일단 맨 앞 5개의 행 데이터만 먼저 뽑아보자.
df.head()

여기서 blood에 해당하는 시리즈만 보고 싶다.
다음과 같이 2가지 방법으로 blood 시리즈만 뽑을 수 있다.
df['blood']
df.blood

왜 2가지 경우를 제공하는 것일까?
만약에 "blood"를 다음과 같이 변수에 저장했다고 해보자.
a = "blood"
그러면 다음과 같이 사용할 수도 있지 않을까?
df[a]
df.a
결론적으로, [] 안에 변수 여러 개를 넣을 수도 있고, for 문을 돌리거나 변수를 이용해 내가 원하는
데이터프레임의 시리즈를 뽑을 수가 있다.
첫번째 경우는 프로그램의 확장성을 위한 것이고, 두번째는 가독성을 위해 제공한 것이다.
추가로, 아까 우리가 데이터 맨 위 3개의 행을 가져오는 방법을 공부했었다.
df.head(3)
이와 똑같은 의미의 코드를 다음과 같이 작성할 수 있다.
df[:3]
열은 다 가져오고, 행은 0부터 2까지 3개 가져오라는 의미이다.
이번엔 확장된 개념은 로케이션 인덱싱(loc)에 대해 알아보자.
로케이션 인덱싱은 컬럼 인덱싱인데 다음과 같이 사용한다.
# 컬럼 인덱싱
# df.loc(행관련인덱싱, 열관련인덱싱)
df.loc[:,'name']
행은 모두 가져오라는 뜻이고, 열은 'name' 컬럼만 가져오라는 뜻이다.
결국 다음과 같은 코드이다.
df['name']
그런데 굳이 df['name'] 이 있는데 왜 확장된 개념을 제공하는 것일까?
다음과 같은 예시를 한번 더 살펴보자.
df.loc[2:5, 'name']
이것은 2부터 5까지의 행을 가져오고, 그 중 'name' 컬럼만 가져오라는 것이다.
이것은 앞서 배운 슬라이싱과 조금 달라서 뒷 값인 5를 포함한다는 점을 꼭 기억해두자.
그럼 컬럼을 여러 개 가져오려면 어떻게 해야 할까? 여러 개를 뭉칠려면 리스트로 뭉치면 된다.
다음과 같이 말이다.
df.loc[2:5, ['name','gender','height']]
그렇다면, 이번엔 행 중에 2번과 5번만 뽑아오고 싶다고 해보자.
그러면 다음과 같이 작성할 수 있다.
df.loc[[2,5], ['name','gender','height']]
범위를 지정해주는 것과 하나 하나 인덱스를 찍는 것을 구분해야 할 것 같다.
다음과 같은 예제는 무슨 의미일까?
df.loc[2:5, 'name':'gender']
행은 2부터 5까지이고, 컬럼은 name 컬럼부터 gender 컬럼까지 출력해달라는 의미이다.

이번엔 더 확장된 개념인 iloc(index-location)에 대해 알아보자.
다음과 같은 예시를 보자.
df.iloc[:,0]
한마디로 인덱스 숫자만으로 로케이팅을 하는 것이다.
위 의미는 행은 모두 가져오고, 컬럼은 0번째 컬럼만 가져오라는 것이다.
다음과 같은 예시를 살펴보면 어떨까.
df.iloc[:,0:3]
이것은 모든 행을 가져오고, 컬럼은 0부터 2까지 가져오라는 것이다. (이번에는 끝 값을 포함하지 않는다)
이번엔 인덱싱에 조건식을 넣는 방식을 알아보자.
df['height'] >= 180

결과값이 각 인덱스 별로 bool 타입으로 결과가 나온다. 이것을 통해 bool Indexing을 할 수가 있다.
df[df['height'] >= 180]
이것은 데이터프레임에서 True인 것만 뽑아서 나올 것이다.

여기서 조금 더 확장(응용)해서 이 중 이름만 보고 싶다면 어떻게 해야할까?
다음과 같이 할 수 있다.
df[df['height'] >= 180]['name']
이것을 loc를 통해 만들면 다음과 같이 할 수도 있다.
df.loc[df['height'] >= 180,'name']
결측값
결측값(Missing Value)은 데이터가 존재하지 않거나 비어있는 값을 의미하고, 보통 NaN 형태로 표현된다.
예를 들어, 키 / 몸무게 / 나이 같은 정보가 입력되지 않았거나 수집 과정에서 누락된 경우 발생한다.
데이터 분석과 머신러닝에서는 결측값이 매우 중요하며, 그대로 두면
평균 계산, 통계 분석, 모델 학습과정에서 오류나 성능저하가 발생할 수 있다.
결측값 처리는 데이터 품질을 높이고 정확한 분석 결과를 얻기 위한 매우 중요한 데이터 전처리 과정이다.
결측값 처리의 방법에는 몇가지가 있는데,
첫번째, NaN이 들어있는 행을 과감히 지워버리는 것이다.
두번째, NaN 데이터 값에 평균값을 넣는 것이다.
세번째, NaN 데이터 값에 중위값을 넣는 것이다.
만약 비어있는 값이 blood 처럼 str 형식인 경우는 어떻게 해야할까?
많이 나온 데이터 값을 넣는 것을 생각해볼 수 있다 (최빈값 넣기)
또, 날씨 데이터 중에 컬럼이 강수량이 있는 경우를 생각해보자.
강수량의 데이터가 몇개가 빠져 있다면? 이 때는 0을 넣는 방법도 생각해볼 수 있다.
이런 식으로 결측값을 지우거나, 다른 것으로 채우는 방안을 데이터를 파악해서 결정해야 한다.
현재 결측치가 다음과 같이 있다고 해보자.

이제, 결측치 여부를 알아보는 메소드를 알아보자.
df.isna()

또, 비슷한 의미인 isnull() 메소드도 있다.
df.isnull()

이번엔 반대의 의미인 notnull()을 살펴보자.
df.notnull()

이제 이것들을 이용해 불린 인덱싱하면 우리가 원하는 결과를 얻을 수 있다.
df[df['blood'].isna()]

그리고 이번엔 테스트를 위해 height의 일부값들을 NaN으로 바꾸기 위해 다음과 같은 코드를 적용할 것이다.
import numpy as np
df.loc[8, 'height'] = np.nan
df.loc[19, 'height'] = np.nan

이것을 기준으로 height이 NaN인 행을 살펴보면 다음과 같다.
df[df['height'].isna()]

이 상황에서 원본 데이터프레임인 df의 오염을 방지하기 위해 데이터프레임을 복사해서 사용하자.
df_copy = df.copy()
이제 df_copy에서 결측값을 채우는 처리를 해보자. 먼저 결측값을 채워주는 함수는 다음과 같다.
df_copy['height'].fillna(0, inplace=True)
지금은 결측값을 0으로 채워주라는 의미이고, inplace=True 셋팅은 실제로 메모리까지 적용되는 설정이다.
여기서는 실제로 0으로 채우고 싶지는 않으므로 다음과 같이 확인만 할 것이다.
df_copy['height'].fillna(0)
이 상태에서, 평균값을 한번 구해보자. 평균값 구하는 메소드는 다음과 같다.
height = df_copy['height'].mean()
이 평균값을 실제로 결측치에 적용할 것이다.
df_copy['height'] = df_copy['height'].fillna(df_copy['height'].mean())

실제로 평균값인 178.527778 값이 결측치 대신 적용되었다!
이번에는 다시 다음과 같이 초기화 하고 중위값을 넣어볼 수 있다.
df_copy = df.copy()
height = df_copy['height'].median()
df['height'] = df['height'].fillna(height)

중위값인 180.5가 결측치 대신 잘 입력되었다!
이번에는 NaN에 해당하는 데이터를 지워버리는 방법을 알아보자.
df_copy = df_copy.dropna()
이것은 결측값이 있는 행 또는 열을 제거하는데, 결측값이 한개라도 있는 경우 삭제한다.
axis 값을 설정할 수 있는데, 기본값은 0으로써 행 삭제이고, 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'] = '대한민국'

만약 여기서 name이 김사과인 행의 nation 컬럼을 미국으로 바꾸고 싶다면 다음과 같이 또 해주면 된다.
df_copy.loc[df_copy['name'] === '김사과', 'nation'] = '미국'
이번엔 행을 제거하는 방법을 알아보자.
df_copy.drop(20, axis=0)
다음과 같이 매개변수로 제거할 행또는 열의 인덱스와, axis 값을 통해 제거할 것이 행인지 열인지 명시해주면 된다.
앞서와 똑같이 0은 행이고 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()) # 표준편차
여기서 평균과 중앙값은 무슨 차이일지 정리해보자. 동시에 분산과 표준편차도 알아보자.
평균과 중앙값
평균(Mean)은 데이터의 모든 값을 더한 뒤 데이터 개수로 나눈 값으로 전체 데이터의 중심적인
경향을 나타내는 대표적인 통계값이다. 반면, 중앙값은 데이터를 크기순으로 정렬했을 때
가장 가운데 위치한 값을 의미하며, 이상치의 영향을 적게 받는다.
평균은 전체 데이터를 모두 반영하는 대표 값이고
중앙값은 데이터의 정중앙 위치를 나타내는 대표값이라고 할 수 있다.
분산 & 표준편차
분산(Variance)은 데이터가 평균으로부터 얼마나 퍼져있는지 수치로 나타낸 값으로
각 데이터와 평균의 차이를 제곱한 뒤 평균을 내어 계산한다.
값이 클수록 데이터의 흩어짐이 크다는 의미이며, 값이 작을수록 평균에 모여있다는 의미이다.
하지만 분산은 차이를 제곱해서 계산하므로, 단위가 원래 데이터와 달라진다는 단점이 있다.
이를 해결하기 위해 사용하는 것이 표준편차(Standard Deviation)이며
표준편차는 분산에 제곱근을 취한 값이다. 따라서 원래 데이터와 같은 단위를 사용해
해석이 더 직관적이다.

그리고 이 통계 함수를 사용할 때는 다음과 같이 groupby() 함수를 맺어 사용할 수 있다.
df_copy.groupby('blood').count()

또, 데이터 분석할 때나 머신러닝/딥러닝 전처리에 많이 사용되는 다음 함수를 알아보자.
df_copy['blood'].value_counts()
다음 함수는 열의 각 값에 대한 데이터를 반환하고, 기본은 NaN을 생략한 채로 반환한다.

데이터 프레임 다루기
다음과 같은 2개의 데이터가 있다고 해보자.
df1 = pd.read_csv("./광고모델_브랜드평판.csv")
df2 = pd.read_csv("./광고모델_상세정보.csv")


데이터 오염을 방지하기 위해 2개의 데이터프레임 모두 복사해주자.
df1_copy = df1.copy()
df2_copy = df2.copy()
여기서 두 개의 데이터프레임을 연결하는 함수를 알아보자.
concat은 두 개의 데이터프레임을 연결해주는 역할을 한다. 행으로 다음과 같이 연결한다.
pd.concat([df1, df1_copy])

처음 0번부터 19번까지가 원래 데이터인데 밑에 0번부터 19번까지가 한번 더 합쳐졌다.
그러나 여기서 0부터 19번까지 인덱스가 2번 출력되어 보기가 좋지 않다.
여기서 인덱스를 리셋하는 방법이 있다.
df1_concat = pd.concat([df1, df1_copy])
df1_concat.reset_index(drop=True)
이렇게 되면 다음과 같이 인덱스가 리셋된다.

인덱스가 리셋되어 20부터 다시 시작하는 걸 볼 수 있다.
그리고 여기서 drop = True 셋팅은 새로운 인덱스 컬럼이 생기는 것을 방지할 수 있다.
다음처럼 기존 인덱스가 drop이 되는 형태인 것이다. 다음과 같이 생기는 걸 방지하는 것이다.

이제 옆으로 붙이는 (컬럼 단위로 붙이는) 것을 해보자.
pd.concat([df1_copy, df2_copy], axis=1)
axis 값을 1로 주어 붙이는 기준을 컬럼으로 설정해주면 된다.

이렇게 컬럼으로 합쳐지는 것이다. 그러나 결합할 때 합치는 기준이 인덱스 기준이다. (즉, 중복되는 컬럼이 나올 수 있다.)
즉, 2개의 데이터프레임이 합쳐질 수 있었던 건 두 데이터프레임의 인덱스가 완전히 일치했기 때문이다.
그럼 일부 행들을 drop 시켜서 인덱스가 완전히 일치하지 않는다면 어떻게 될까? 다음과 같이 살펴보자.
df2_copy = df2_copy.drop([1, 3, 5, 7, 9])

보시다시피 1, 3, 5, 7, 9번 행이 데이터프레임에서 지워졌다.
이 상태에서 다시 concat 함수를 통해 합쳐보자.
pd.concat([df1_copy, df2_copy], axis=1)

일단 인덱스 번호 일부가 지워지긴 했지만, 인덱스 번호가 흐트러진게 아니므로, 같은 번호끼리는 잘 합쳐질 것이다.
(비어있는 인덱스는 보기와 같이 NaN 처리된다)
이제 2번째 데이터프레임을 인덱스 재정렬하고 한번 확인해보자.
df2_copy = df2_copy.reset_index(drop=True)

0번부터 14번까지로 재정렬 되었으므로 인덱스가 흐트러졌다고 할 수 있다.
이제 첫번째 데이터프레임 인덱스와 두번째 데이터프레임 인덱스가 다른 상황이다.
여기에다가 또 데이터를 하나 더 추가해보자.
dic = {
'이름': '김사과',
'연봉': 99000000,
'주소': '서울 강남구'
}
df2_copy.loc[len(df2_copy)] = dic

이제 이 상황에서 concat을 통해 합쳐보자.
pd.concat([df1_copy, df2_copy], axis=1)

인덱스를 기준으로 합쳐지므로, 같은 인덱스의 이름이 서로 다르게 합쳐지는 오류가 발생한다.
즉, 인덱스 번호가 다르면 열로 붙일 수 없는 문제가 발생한다.
이제 그 문제점을 해결하는 merge 함수에 대해 알아보자.
pd.merge(df1_copy, df2_copy, on="이름", how="left")

이것은 특정 고유한 키(key) 값을 기준으로 합치는데 내가 원하는 열을 보고 서로 열이 같은 것 끼리 합칠 수 있다는 것이다.
그래서 on에 유니크 값(키값)에 해당하는 컬럼을 설정해주면 된다. 여기서는 이름 컬럼으로 설정했다.
how에는 병합의 기준을 넣어야 하는데, left / right / inner / cross 4가지 값이 있다.
기준값을 보면 알겠지만 RDB의 JOIN 연산과 매우 유사하다.
how = "left"
먼저, LEFT의 의미는 왼쪽 데이터프레임을 기준으로 먼저 합쳐달라는 의미이다. 여기서 왼쪽 데이터프레임은 df1_copy인데
df1_copy의 데이터는 다 뿌리고, 그것에 맞춰서 df2_copy를 합쳐달라는 의미이다.
다음과 같이 가정해보자.
왼쪽 데이터의 행의 갯수가 20개이고, 오른쪽 데이터 행의 갯수가 14개라고 가정한다.

그러면 20개를 그대로 보여주고, 오른쪽 데이터 프레임을 왼쪽 프레임에 맞춰서 보여주는 형태이다.
오른쪽 프레임에 맞지 않는 컬럼의 값에 NaN이 나온 것도 Left 연산이기 때문이다.

how = "right"
이것을 오른쪽 기준으로 합쳐달라는 의미이다. 방금 가정한 것에서 예시를 적용하자면

여기서 오른쪽을 기준으로 합쳐져야 하므로, 왼쪽 데이터프레임 중 오른쪽 데이터프레임과 겹치는 부분만
출력될 것이고, 최종적으로 14개의 행만 보여질 것이다.


왼쪽 데이터프레임 중에 오른쪽 데이터프레임에 포함되어 있지 않는 데이터는 당연히 제외될 것이다.
(즉, 왼쪽 데이터프레임이 오른쪽 데이터프레임 기준으로 맵핑되는 것만 포함될 것이라는 얘기이다.)
다만 오른쪽 데이터프레임 기준으로 출력되는 것이므로 포함되지 않는 데이터는 NaN 포함된 채로 출력될 것이다.

단 여기서 세부적인 사항에 따라 inner 연산이냐 cross 연산이냐로 나뉘는데
inner 연산은 서로 교집합인 경우에만 출력되는 경우이다.

cross 연산은 왼쪽 데이터프레임의 한 사람당, 오른쪽 데이터프레임의 모든 데이터들을 다 연결해보는 것이다.
집합 연산의 카디널 프로덕트(곱 연산)과 비슷하다.

날짜 타입
이제 날짜 부분을 처리하기 위해 다시 데이터프레임의 info를 확인해보자.
df_copy.info()

현재 생년월일에 해당하는 birthday 컬럼의 데이터 값의 데이터 타입은 object(str)이다.
이것을 날짜 타입으로 한번 바꾸어보자. 다음과 같이 하자.
df_copy['birthday'] = pd.to_datetime(df_copy['birthday'])

그러면 날짜 타입으로 변경됨을 확인할 수 있다.
그러면 다음과 같은 속성 접근으로 날짜만 뽑을 수 있다.
df_copy['birthday'].dt.year # 년도
df_copy['birthday'].dt.month # 월
df_copy['birthday'].dt.day # 일
df_copy['birthday'].dt.hour # 시간
df_copy['birthday'].dt.minute # 분
df_copy['birthday'].dt.second # 초
df_copy['birthday'].dt.dayofweek # 요일
df_copy['birthday'].dt.isocalendar().week # 1년중에 몇번째 주인지
자주 사용하는 함수
apply
데이터프레임이나 시리즈의 데이터를 사용자 정의 함수 혹은 내장 함수에 적용하여
새로운 값을 계산하거나 변환할 때 사용한다. 데이터를 행 또는 열 단위로 처리하는 강력한 도구이다.

다음과 같은 데이터가 있을 때 성별을 남자는 1, 여자는 0으로 변환하고 싶다고 해보자.
(머신러닝/딥러닝 때 필요한 수치화)
그럼 다음과 같이 할 수 있을 것이다.
df_copy.loc[df_copy['성별'] == '남자', '성별'] = 1
df_copy.loc[df_copy['성별'] == '여자', '성별'] = 0
이것을 함수를 통해서 바꾸는 방법을 생각해보자.
def male_or_female(x):
if x == '남':
return 1
elif x == '여':
return 0
else:
return None
그러면 다음과 같이 적용시킬 수 있다.
df_copy['성별'].apply(male_or_female)'AI > Python' 카테고리의 다른 글
| 데이터 분석 - 넘파이 (1) (0) | 2026.05.23 |
|---|---|
| Python의 기본 - 모듈 (12) (0) | 2026.05.23 |
| Python의 기본 - 매직 메서드 (11) (0) | 2026.05.23 |
| Python의 기본 - 예외 처리 (10) (0) | 2026.05.23 |
| Python의 기본 - 객체 지향 프로그래밍의 패러다임 (9) (0) | 2026.05.17 |