Pandas DataFrame 이해하기

표 형태의 데이터를 다룰 때 파이썬에서 가장 많이 쓰는 게 Pandas DataFrame이다. DataFrame은 크게 세 요소로 이뤄진다 — 행을 식별하는 index, 열을 식별하는 columns, 그리고 실제 값들인 data. 이 글에서는 DataFrame을 만드는 방법부터 index/column 구조, Series와의 차이, 자주 쓰는 함수까지 정리한다.

DataFrame 만들기

가장 흔한 방법은 딕셔너리를 넘기는 것이다. 키는 컬럼 이름, 값은 각 컬럼의 데이터가 된다.

import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Charlie"],
    "age": [25, 30, 35],
    "city": ["Seoul", "Busan", "Incheon"],
})
print(df)
      name  age     city
0    Alice   25    Seoul
1      Bob   30    Busan
2  Charlie   35  Incheon

2차원 리스트에 columns를 따로 지정해서 만들 수도 있다.

df2 = pd.DataFrame([[1, 2], [3, 4]], columns=["a", "b"])
print(df2)
   a  b
0  1  2
1  3  4

이 밖에도 pd.read_csv(), pd.read_excel()처럼 파일에서 바로 읽어 들이는 방법도 실무에서 자주 쓴다.

index와 column

DataFrame에서 index와 column은 파이썬 리스트의 인덱스, 딕셔너리의 key처럼 row와 column을 구분하고 데이터를 찾아가는 주소 역할을 한다. 별도로 지정하지 않으면 index는 0부터 시작하는 정수(RangeIndex)가 자동으로 붙는다.

print(df.index)
print(df.columns)
RangeIndex(start=0, stop=3, step=1)
Index(['name', 'age', 'city'], dtype='str')

(Pandas 2.x에서는 문자열 컬럼의 dtype이 str 대신 object로 표시된다. Pandas 3.0부터 문자열 전용 dtype이 기본으로 도입되면서 표시가 바뀌었다.)

set_index()로 특정 컬럼을 index로 승격시킬 수 있다.

print(df.set_index("name"))
         age     city
name
Alice     25    Seoul
Bob       30    Busan
Charlie   35  Incheon

DataFrame vs Series

Series는 index가 붙은 1차원 배열이고, DataFrame은 같은 index를 공유하는 여러 Series를 열로 모아놓은 2차원 구조라고 보면 된다. DataFrame에서 컬럼 하나를 []로 꺼내면 Series가 되고, [[]](리스트로 감싸서)로 꺼내면 컬럼 하나짜리 DataFrame이 된다.

s = df["age"]
print(type(s))
print(s)
<class 'pandas.core.series.Series'>
0    25
1    30
2    35
Name: age, dtype: int64

반면 df[["age"]]<class 'pandas.core.frame.DataFrame'>이 된다. 컬럼이 하나만 필요해도 DataFrame 형태를 유지하고 싶을 때 이 차이를 활용한다.

주요 함수

df.info()

컬럼별 dtype, non-null 개수, 메모리 사용량을 한눈에 보여준다. 데이터를 받고 나서 구조를 파악할 때 가장 먼저 실행하게 되는 함수다.

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   name    3 non-null      str
 1   age     3 non-null      int64
 2   city    3 non-null      str
dtypes: int64(1), str(2)
memory usage: 204.0 bytes

df.head() & df.tail()

각각 앞/뒤 n개 행만 보여준다. 인자를 생략하면 기본값은 5다.

print(df.head(2))
print(df.tail(1))
    name  age   city
0  Alice   25  Seoul
1    Bob   30  Busan
      name  age     city
2  Charlie   35  Incheon

df.rename()

컬럼이나 index의 이름을 바꾼다. 딕셔너리로 {기존이름: 새이름}을 넘기면 지정한 것만 바뀌고 나머지는 그대로 유지된다. 원본을 바꾸려면 inplace=True를 추가한다.

print(df.rename(columns={"name": "이름", "age": "나이"}))
        이름  나이     city
0    Alice  25    Seoul
1      Bob  30    Busan
2  Charlie  35  Incheon

axis 파라미터

df.axis라는 함수는 없고, 대신 df.axes 속성으로 [index, columns]를 함께 확인할 수 있다.

print(df.axes)
[RangeIndex(start=0, stop=3, step=1), Index(['name', 'age', 'city'], dtype='str')]

실제로 더 자주 쓰는 건 sum(), drop(), apply() 같은 메서드에 넘기는 axis 인자다. axis=0은 index 방향(행을 따라 내려가며 컬럼별로 집계), axis=1은 columns 방향(컬럼을 따라 가로질러 행별로 집계)을 뜻한다.

df3 = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
print(df3.sum(axis=0))
print(df3.sum(axis=1))
a    3
b    7
dtype: int64
0    4
1    6
dtype: int64

주의사항

  • df["col"]는 Series, df[["col"]]는 DataFrame이라는 차이를 헷갈리면 이후 연산에서 타입 오류가 나기 쉽다.
  • rename(), drop() 같은 메서드는 기본적으로 원본을 바꾸지 않고 새 DataFrame을 반환한다. 원본에 바로 반영하려면 inplace=True를 명시하거나 반환값을 다시 변수에 대입해야 한다.
  • axis 값은 라이브러리/메서드마다 방향 해석이 조금씩 다르게 느껴질 수 있어서, 결과가 예상과 다르면 axis=0/axis=1을 바꿔가며 확인해보는 게 빠르다.

마무리

DataFrame은 index, columns, data 세 요소로 이뤄진 2차원 표 구조이고, 컬럼 하나를 꺼내면 Series가 된다는 관계만 이해하면 나머지 함수들은 대부분 이 구조 위에서 동작하는 것들이다. info()로 구조를 파악하고, head()/tail()로 일부만 훑어보는 습관을 들이면 데이터를 다루는 속도가 확실히 빨라진다.

참고 사이트

답글 남기기