Py) 기초 - Pandas(DataFrame)

Py) 기초 - Pandas(DataFrame)

파이썬 기반 데이터분석을 위하여 Pandas 라이브러리의 데이터프레임(DataFrame) 객체에 대해 간단하게 알아본다.


개요

Pandas 라이브러리의 기본 객체이자 2차원 객체인 데이터프레임(DataFrame) 객체는 2차원 배열과 유사하지만, 각각 열과 행에 라벨을 붙일 수 있다. 이 라벨을 통해 인덱싱(indexing)이 가능하며, 인덱싱을 통해 원소에 접근할 수 있다. 그리고 NumPy 의 어레이 객체보다 강력하고 다양한 메서드를 지원하며 심지어 간단한 그래프도 쉽게 그릴 수 있다. 그리고 시리즈(Series) 객체를 여러개 이어붙인 것이 데이터프레임이라고 할 수 있다.

데이터프레임은 엄밀히 말하면 인덱스가 행과 열에 각각 있는데 일반적으로 데이터프레임에서 인덱스라고 하면 행(row)의 인덱스를 뜻하며 열의 인덱스는 변수명(column name)으로 지칭한다. 그리고 인덱스/변수명/값 각 요소는 .index/.columns/.values 어트리뷰트로 접근할 수 있다.
Pandas 데이터프레임 구조

생성

데이터프레임 객체의 생성은 DataFrame() 함수에 리스트나 NumPy 어레이 객체를 입력으로 넣어 생성할 수 있으며 딕셔너리를 사용하여 생성하는 경우도 많다.

비어있는 데이터프레임 객체를 생성할 경우 함수 내부에 아무것도 쓰지 않는다.

1
2
pd.DataFrame()
## __

다음은 리스트, NumPy 어레이, 딕셔너리를 사용하여 데이터프레임 객체를 생성하는 예제이다. 시리즈와 다르게 리스트는 중첩이고 어레이는 2차원을 입력으로 하는 것을 알 수 있다.

리스트 기반 생성

1
pd.DataFrame([[1, 2, 3]])
0 1 2
0 1 2 3
1
pd.DataFrame([[1, 2], [3, 4]])
0 1
0 1 2
1 3 4

NumPy 어레이 기반 생성

1
2
3
4
5
arr1 = np.array([[1, 2, 3]])
arr1
## array([[1, 2, 3]])

pd.DataFrame(arr1)
0 1 2
0 1 2 3
1
2
3
4
5
6
arr2 = np.array([[1, 2], [3, 4]])
arr2
## array([[1, 2],
## [3, 4]])

pd.DataFrame(arr2)
0 1
0 1 2
1 3 4

딕셔너리 기반 생성

딕셔너리의 키는 데이터프레임 객체의 변수명으로 된다.

1
2
pd.DataFrame({"col1": [1, 2],
"col2": [3, 4]})
col1 col2
0 1 3
1 2 4
1
2
pd.DataFrame(dict(col1 = [1, 2],
col2 = [3, 4]))
col1 col2
0 1 3
1 2 4

인덱스 지정

데이터프레임 객체 생성시 리스트나 어레이를 사용할 경우 변수명에 0부터 1씩 증가하는 값이 자동 부여되는데 직접 지정하고자 한다면 columns 인자에 값을 입력하면 된다.

1
2
pd.DataFrame([[1, 2], [3, 4]],
columns = ["col1", "col2"])
col1 col2
0 1 2
1 3 4

상기 결과를 보면 시리즈 객체처럼 인덱스가 직접적으로 표기된 것을 알 수 있다. 시리즈 객체와 마찬가지로 데이터프레임 또한 인덱스기반 연산도 가능하다.

인덱스를 지정하는 데이터프레임 객체 생성은 다음과 같다.

1
2
pd.DataFrame([[1, 2], [3, 4]],
index = [100, 200])
0 1
100 1 2
200 3 4

주의점

데이터프레임을 생성할 때는 각 변수에 할당되는 원소의 개수가 같아야 된다. 만약 다음과 같은 코드로 변수를 생성하고자 한다면 에러가 발생하는 것을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
pd.DataFrame(dict(col1 = [1, 2],
col2 = [3, 4, 5]))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_21084\1626876745.py in <module>
----> 1 pd.DataFrame(dict(col1 = [1, 2],
2 col2 = [3, 4, 5]))

......

ValueError: All arrays must be of the same length

상기 코드의 경우 “col1” 변수에 원소를 2개 입력하고 “col2” 변수에 원소를 3개 입력하였는데 그 개수가 서로 맞지 않아 에러가 발생하였다. 즉, “col1” 변수에 입력할 원소를 3개로 바꾸거나 “col2” 변수에 입력할 원소를 2개로 바꾸어 각 변수에 할당되는 객체의 원소 개수를 통일해야 올바르게 데이터프레임을 생성할 수 있다.

인덱싱

데이터프레임 객체의 인덱싱은 시리즈 객체와 같이 크게 인덱서(indexer)를 사용하는 것과 사용하지 않는 것이 있다.

먼저 다음의 데이터프레임을 준비한다.

1
2
3
4
df = pd.DataFrame(dict(col1 = [100, 200, 300],
col2 = [400, 500, 600],
col3 = [700, 800, 900]))
df
col1 col2 col3
0 100 400 700
1 200 500 800
2 300 600 900

기본 인덱싱

온점 또는 대괄호를 사용하여 내부 원소에 접근할 수 있다. 그리고 인덱싱 결과가 1차원인 경우 별도의 조치(리스트 감싸기, .to_frame() 또는 .reset_index() 메서드 사용 등)가 없다면 시리즈 또는 1개의 개별 원소만 반환된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
df.col1
## 0 100
## 1 200
## 2 300
## Name: col1, dtype: int64

df["col1"]
## 0 100
## 1 200
## 2 300
## Name: col1, dtype: int64

df["col2"][0]
## 400

df["col2"][-2:]
## 1 500
## 2 600
## Name: col2, dtype: int64

데이터프레임 객체의 특정 변수의 원소에 접근하고자 할 때 어트리뷰트에 접근하는 것 처럼 마침표(.)를 사용한 코드를 작성하는 사람들이 있다. 이는 대괄호를 사용한 것 보다 입력해야하는 특수문자가 더 적기 때문에 선호하는 경향이 큰데 이는 주의해야한다. 다음의 코드를 보자.

1
2
3
df_t = pd.DataFrame(dict(shape = ["A", "B"],
type = ["C", "D"]))
df_t
shape type
0 A C
1 B D
1
2
3
4
5
6
7
df_t.shape
## (2, 2)

df_t["shape"]
## 0 A
## 1 B
## Name: shape, dtype: object

“df_t” 객체의 “shape”변수의 원소를 반환하는 코드를 작성하였으나 “df_t.shape”코드의 경우 “shape”변수의 원소가 아닌 데이터프레임 객체의 “shape”어트리뷰트가 반환되는 결과를 확인할 수 있다. 이 때문에 데이터프레임 객체의 변수명이 데이터프레임 객체의 어트리뷰트명과 겹치게 된다면(충돌이 난다면) 우선순위가 변수명 보다 어트리뷰트가 높기 때문에 사용자가 원하는 결과가 나오지 않을 수 있다. 그래서 되도록이면 마침표를 사용한 데이터프레임 객체의 변수 접근 보다는 대괄호를 사용하는 것을 권장한다.

인덱서의 사용

인덱서는 시리즈와 마찬가지로 순수하게 정수만 사용가능한 .iloc[]와 다양한 입력을 받는 .loc[] 두 가지를 사용할 수 있다. 그런데 데이터프레임은 2차원 객체이기 때문에 인덱서의 대괄호 내부에 쉼표를 쓰고 쉼표 앞에 row(행)에 대한 지정이나 연산을 위한 코드를 작성하고, 쉼표 뒤에 column(열)에 대한 지정이나 연산을 위한 코드를 작성한다.
Pandas 데이터프레임 인덱서 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
df.iloc[1, ]
## col1 200
## col2 500
## col3 800
## Name: 1, dtype: int64

df.iloc[:, 1]
## 0 400
## 1 500
## 2 600
## Name: col2, dtype: int64

df.iloc[:1, 1]
## 0 400
## Name: col2, dtype: int64

모든 열을 선택하고자 할 때는 쉼표 뒷부분을 생략하여 표현할 수 있지만, 모든 행을 선택하고자 할 때는 쉼표 앞부분을 생략할 수 없고 콜론(:)을 써야한다. 이 부분이 생소하고 불편할 수 있지만 데이터프레임 객체가 이렇게 설계되었기 때문에 어쩔 수 없이 적응하고 써야 한다.

데이터프레임 인덱싱의 경우 기본적으로 열 또는 행 하나만 선택하는 경우 그 결과가 시리즈 객체로 나오나 다음과 같이 리스트를 사용할 경우 데이터프레임으로 객체 유형이 변하지 않고 유지된다.

1
df.iloc[:, [1]]
col2
0 400
1 500
2 600
1
df.iloc[:1, [1]]
col2
0 400
1
df.iloc[:1, 1:]
col2 col3
0 400 700
1
df.iloc[:1, [0, 2]]
col1 col3
0 100 700

.loc[] 인덱서의 경우 연속범위 지정 부분에서 .iloc[]와 다르니 참고하도록 하자.

1
2
3
4
5
6
7
df.loc[0, ]
## col1 100
## col2 400
## col3 700
## Name: 0, dtype: int64

df.loc[:1, ]
col1 col2 col3
0 100 400 700
1 200 500 800
1
df.loc[:, "col2":]
col2 col3
0 400 700
1 500 800
2 600 900
1
df.loc[1:, "col2":]
col2 col3
1 500 800
2 600 900
1
df.loc[:, ["col1", "col3"]]
col1 col3
0 100 700
1 200 800
2 300 900
1
df.loc[:, ["col2"]]
col2
0 400
1 500
2 600

필터링

시리즈 객체의 필터링은 .loc[] 인덱서를 사용하는 경우와 그렇지 않은 경우로 나뉘어진다. 그러나 실제 사용시 크게 체감을 느끼지 못하기 때문에 많이들 인덱서를 사용하지 않는 방향으로 코드를 작성한다.

먼저 다음과 같이 데이터를 준비한다.

1
2
3
4
df2 = pd.DataFrame(dict(v1 = [100, 200, 300],
v2 = [400, 500, 600],
v3 = ["A", "B", "C"]))
df2
v1 v2 v3
0 100 400 A
1 200 500 B
2 300 600 C
1
df2.loc[df2["v1"] == 200, ]
v1 v2 v3
1 200 500 B
1
df2.loc[df2["v1"] != 200, ]
v1 v2 v3
0 100 400 A
2 300 600 C
1
df2.loc[df2["v2"] >= 500, ]
v1 v2 v3
1 200 500 B
2 300 600 C
1
df2.loc[df2["v3"] == "A", ]
v1 v2 v3
0 100 400 A
1
df2.loc[df2["v3"] != "A", ]
v1 v2 v3
1 200 500 B
2 300 600 C

상기 필터링 예제 이외에도 문자열 원소와 관련된 필터링, 2개 이상의 조건, 관련 메서드를 활용한 필터링이 있으나 별도의 Pandas(필터링) 게시글 에서 다룬다.

메서드

시리즈는 지원하는 메서드가 너무 많아 별도의 포스팅과 함게 다룰 예정이다. 관련 내용은 Pandas 공식 문서 페이지를 참고하도록 하자.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×