Pandas DataFrame 데이터 조작

@winuss · October 03, 2020 · 9 min read

Pandas는 Numpy의 2차원 배열에서 가능한 대부분의 데이터 처리가 가능하며 추가로 데이터 처리 및 변환을 위한 다양한 함수와 메서드를 제공한다.

데이터 갯수 세기

가장 간단한 데이터 분석은 데이터의 갯수를 세는 것이나. count 메서드를 사용하는데, 주의할 점은 NaN값은 세지 않는다는 것이다.

import pandas as pd 
import numpy as np
s = pd.Series(range(10))
s[3] = np.nan
s
0    0.0
1    1.0
2    2.0
3    NaN
4    4.0
5    5.0
6    6.0
7    7.0
8    8.0
9    9.0
dtype: float64
s.count()
9

데이터프레임에서는 각 열마다 별도로 데이터 갯수를 센다. 데이터에서 값이 누락된 부분을 찾을 때 유용하다.

np.random.seed(2)
df = pd.DataFrame(np.random.randint(5, size=(4,4)), dtype=float)
df.iloc[2,3] = np.nan
df
0 1 2 3
0 0.0 0.0 3.0 2.0
1 3.0 0.0 2.0 1.0
2 3.0 2.0 4.0 NaN
3 4.0 3.0 4.0 2.0
df.count()
0    4
1    4
2    4
3    3
dtype: int64

카테고리 값 세기

Series의 값이 정수, 문자열, 카테고리 값인 경우에는 value_counts 매서드로 각각의 값이 나온 횟수를 셀 수 있다.

np.random.seed(1)
s2 = pd.Series(np.random.randint(6, size=100))
s2.tail()
95    4
96    5
97    2
98    4
99    3
dtype: int32
s2.value_counts()
1    22
0    18
4    17
5    16
3    14
2    13
dtype: int64

DataFrame에는 value_counts 매서드가 없으므로 각 열마다 별도 확인 해야 한다.

df[0].value_counts()
3.0    2
4.0    1
0.0    1
Name: 0, dtype: int64

정렬

데이터를 정렬하려면 sort_indexsord_values매서드를 사용한다. sort_index는 인덱스 값을 기준으로, sort_values는 데이터 값을 기준으로 정렬한다.

앞서 s2 Series의 각 데이터 값에 따른 데이터 갯수를 보기좋게 정렬하려면 sort_index를 사용한다.

s2.value_counts().sort_index()
0    18
1    22
2    13
3    14
4    17
5    16
dtype: int64

NaN값이 있는 경우에는 정렬하면 NaN값은 마지막에 위치한다.

s.sort_values()
0    0.0
1    1.0
2    2.0
4    4.0
5    5.0
6    6.0
7    7.0
8    8.0
9    9.0
3    NaN
dtype: float64

큰 수에서 작은 수로 반대방향 정렬하려면 ascending=False인수를 지정한다.

s.sort_values(ascending=False)
9    9.0
8    8.0
7    7.0
6    6.0
5    5.0
4    4.0
2    2.0
1    1.0
0    0.0
3    NaN
dtype: float64

DataFrame에서 sort_values 매서드를 사용하려면 by 인수로 정렬 기준이 되는 열을 지정해 주어야 한다.

df.sort_values(by=1)
0 1 2 3
0 0.0 0.0 3.0 2.0
1 3.0 0.0 2.0 1.0
2 3.0 2.0 4.0 NaN
3 4.0 3.0 4.0 2.0

by 인수에 리스트 값을 넣으면 이 순서대로 정렬 기준이 우선순위가 된다.

df.sort_values(by=[1,2])
0 1 2 3
1 3.0 0.0 2.0 1.0
0 0.0 0.0 3.0 2.0
2 3.0 2.0 4.0 NaN
3 4.0 3.0 4.0 2.0

행/열 합계

행과 열의 합계를 구할 때는 sum(axis) 매서드를 사용한다.

  • 행 합계 : axis=1
  • 열 합계 : axis=0 (기본값이므로 생략 가능)
np.random.seed(1)
df2 = pd.DataFrame(np.random.randint(10, size=(4,8)))
df2
0 1 2 3 4 5 6 7
0 5 8 9 5 0 0 1 7
1 6 9 2 4 5 2 4 2
2 4 7 7 9 1 7 0 6
3 9 9 7 6 9 1 0 1
df2.sum(axis=1)
0    35
1    34
2    41
3    42
dtype: int64
df2.sum(axis=0)
0    24
1    33
2    25
3    24
4    15
5    10
6     5
7    16
dtype: int64

행과 열의 합을 행/열에 추가해보자.

df2['row_sum'] = df2.sum(axis=1)
df2
0 1 2 3 4 5 6 7 row_sum
0 5 8 9 5 0 0 1 7 35
1 6 9 2 4 5 2 4 2 34
2 4 7 7 9 1 7 0 6 41
3 9 9 7 6 9 1 0 1 42
df2.loc['col_sum', :] = df2.sum()
df2
0 1 2 3 4 5 6 7 row_sum
0 5.0 8.0 9.0 5.0 0.0 0.0 1.0 7.0 35.0
1 6.0 9.0 2.0 4.0 5.0 2.0 4.0 2.0 34.0
2 4.0 7.0 7.0 9.0 1.0 7.0 0.0 6.0 41.0
3 9.0 9.0 7.0 6.0 9.0 1.0 0.0 1.0 42.0
col_sum 24.0 33.0 25.0 24.0 15.0 10.0 5.0 16.0 152.0

apply 변환

행이나 열 단위로 더 복잡한 처리를 하고 싶을 때는 apply 매서드를 사용한다. 인수로 행 또는 열을 받는 함수를 apply매서드의 인수로 넣으면 각 열(또는 행)을 반복하여 그 함수에 적용시킨다.

df3 = pd.DataFrame({
    'A': [1,3,4,3,4],
    'B': [2,3,1,2,3],
    'C': [1,5,2,4,4]
})
df3
A B C
0 1 2 1
1 3 3 5
2 4 1 2
3 3 2 4
4 4 3 4

예를 들어 각 열의 최대값과 최소값의 차이를 구하고 싶으면 다음과 같은 람다 함수를 넣는다.

df3.apply(lambda x: x.max() - x.min()) # axis=0 기본값
A    3
B    2
C    4
dtype: int64

마찬가지로 행에 대해 적용하고 싶다면 axis=1 인수를 추가해 주면 된다.

df3.apply(lambda x: x.max() - x.min(), axis=1)
0    1
1    2
2    3
3    2
4    1
dtype: int64

각 열에 대해 어떤값이 얼마나 사용되고 있는지 알고 싶다면 value_counts 함수를 넣을 수도 있다.

df3.apply(pd.value_counts) # axis=0 
A B C
1 1.0 1.0 1.0
2 NaN 2.0 1.0
3 2.0 2.0 NaN
4 2.0 NaN 2.0
5 NaN NaN 1.0

NaN 값은 fillna 매서드를 사용하여 원하는 값으로 바꿀 수 있다. astype 매서드로 전체 데이터의 자료형을 바꾸는 것도 가능하다.

# NaN은 0으로 바꾸고 값의 타입을 int로 변경
df3.apply(pd.value_counts).fillna(0).astype(int)
A B C
1 1 1 1
2 0 2 1
3 2 2 0
4 2 0 2
5 0 0 1

실수 값을 카테고리 값으로 변환

실수 값을 크기 기준으로 하여 카테고리 값으로 변환하고 싶을 때는 다음과 같은 명령을 사용한다.

  • cut : 실수 값의 경계선을 지정하는 경우
  • qcut : 갯수가 똑같은 구간으로 나누는 경우
# 나이 데이터
ages = [0, 2, 10, 21, 23, 37, 31, 61, 20, 41, 32, 100]

cut 명령을 사용하면 실수값을 다음처럼 카테고리 값으로 바꿀 수 있다. bins 인수는 카테고리를 나누는 기준값이 된다. 영역을 넘는 값은 NaN으로 처리된다.

bins = [1, 15, 25, 35, 60, 99] # 카테고리 기준값
labels = ["미성년자", "청년", "중년", "장년", "노년"] # 카테고리
cats = pd.cut(ages, bins, labels=labels)
cats
[NaN, '미성년자', '미성년자', '청년', '청년', ..., '노년', '청년', '장년', '중년', NaN]
Length: 12
Categories (5, object): ['미성년자' < '청년' < '중년' < '장년' < '노년']

cut 명령이 반환하는 값은 Categorical 클래스 객체이다. 이 객체는 categories속성으로 라벨 문자열을, codes 속성으로 정수로 인코딩한 카테고리 값을 가진다.

type(cats)
pandas.core.arrays.categorical.Categorical
cats.categories
Index(['미성년자', '청년', '중년', '장년', '노년'], dtype='object')
cats.codes # -1은 기준값에 벗어난 NaN값
array([-1,  0,  0,  1,  1,  3,  2,  4,  1,  3,  2, -1], dtype=int8)
df4 = pd.DataFrame(ages, columns=["ages"])
df4["age_cat"] = pd.cut(df4.ages, bins, labels=labels)
df4
ages age_cat
0 0 NaN
1 2 미성년자
2 10 미성년자
3 21 청년
4 23 청년
5 37 장년
6 31 중년
7 61 노년
8 20 청년
9 41 장년
10 32 중년
11 100 NaN

정말 쉽게 데이터를 정의한 카테고리기준으로 분류 하였다!

qcut 명령은 구각 경계선을 지정하지 않고 데이터 갯수가 같도록 지정한 수의 구간으로 나눈다. 예를 들어 다음 코드는 1000개의 데이터를 4개의 구간으로 나누는데 각 구간은 250개씩 데이터를 가진다.

data = np.random.randn(1000)
cats = pd.qcut(data, 4, labels=["Q1", "Q2", "Q3", "Q4"])
cats
['Q2', 'Q1', 'Q2', 'Q3', 'Q1', ..., 'Q1', 'Q1', 'Q4', 'Q4', 'Q2']
Length: 1000
Categories (4, object): ['Q1' < 'Q2' < 'Q3' < 'Q4']
pd.value_counts(cats)
Q4    250
Q3    250
Q2    250
Q1    250
dtype: int64

성적을 비유하여 생각해보면 cut은 절대평가 기준이 되는 점수에 의해 분류가 되는 것이고 qcut은 상대평가 즉 점수의 순서대로 정렬하고 특정 수만큼씩 분류하는 것이라고 생각하면 될듯 싶다.

출처 : 데이터사이언스 스쿨(datascienceschool.net)

@winuss
Hello :) Developer notes!