Py) 기초 - Pandas(문자 데이터)

Py) 기초 - Pandas(문자 데이터)

파이썬 기반 데이터분석을 위하여 Pandas 라이브러리 기반 문자 데이터를 다루는 방법을 알아보자.


※ 본 내용에서 사용하는 big_mart.csv 파일은 별도로 다운로드 받아야 한다.
big_mart.csv 다운받기 [클릭]
※ 상기 데이터는 Kaggle의 데이터 세트 페이지에서 상세 내용 확인 가능하다.

개요

데이터 분석을 함에 있어서 숫자만 다룰 수 없다. 지역명, 상품명, 상호명, 채팅내역 등 많은 내용이 문자(또는 문자열)로 되어있다. 그래서 내가 원하는 문자가 들어있는 행을 필터링하거나 특정 문자를 제거하는 등 다양한 작업이 필요할 수 있다. 파이썬에서는 문자를 다루기 위한 전문 라이브러리와 함수를 지원하긴 하지만 그 중에서도 Pandas 라이브러리를 사용할 수 있다.

문자 형식의 원소를 가지는 Pandas Series 객체는 .str 접근자(accessor)를 사용하여 관련 어트리뷰트 또는 메서드를 사용할 수 있다. 이와 관련해서 pandas.Series.str.capitalize() 부터 pandas.Series.str.get_dummies() 까지 총 54개의 어트리뷰트와 메서드가 있다.
※ Pandas 2.0.0 기준

실습

데이터 준비

실습을 위해 “big_mart.csv” 파일을 읽어오고 확인해보자.

1
2
3
df = pd.read_csv("big_mart.csv")
df = df.select_dtypes(exclude = "number")
df.head()
ProductID FatContent ProductType OutletID OutletSize LocationType OutletType
0 FDA15 Low Fat Dairy OUT049 Medium Tier 1 Supermarket Type1
1 DRC01 Regular Soft Drinks OUT018 Medium Tier 3 Supermarket Type2
2 FDN15 Low Fat Meat OUT049 Medium Tier 1 Supermarket Type1
3 FDX07 Regular Fruits and Vegetables OUT010 NaN Tier 3 Grocery Store
4 NCD19 Low Fat Household OUT013 High Tier 3 Supermarket Type1

문자열 데이터를 다루기 위해 수치형 변수는 .select_dtypes() 메서드로 제외처리했다.

.len()

.str.len() 메서드는 문자열의 길이를 반환한다. 문자열 길이는 띄어쓰기 같은 특수문자 까지 포함하며 다음의 코드를 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
df["LocationType"].unique()
## array(['Tier 1', 'Tier 3', 'Tier 2'], dtype=object)

df["LocationType"].str.len()
## 0 6
## 1 6
## 2 6
## 3 6
## 4 6
## ..
## 8518 6
## 8519 6
## 8520 6
## 8521 6
## 8522 6
## Name: LocationType, Length: 8523, dtype: int64

“Tier 1” 원소의 문자 개수는 6이라고 산출되는 것을 알 수 있다. 이 메서드는 주로 특정 길이의 원소의 입력이 기대될 때 길이가 다른 문자가 있는지 확인하는 용도로 사용한다. 예를 들어 지금처럼 “LocationType” 변수에 “Tier” 다음에 띄어쓰기가 한 칸 있고 숫자가 하나 뒤따라오는 패턴이 기대될 때 다른 형식 또는 길이의 원소가 있는 경우를 검사하고자 하는 경우 활용하기도 한다. 그렇게 사용하기 위해서는 .value_counts() 메서드와 같이 사용한다.

1
2
3
4
df["LocationType"].str.len().value_counts()
## LocationType
## 6 8523
## Name: count, dtype: int64

만약 새로운 문자 “asdfasdf”가 추가된다면 상기 결과는 다음과 같이 바뀐다.

1
2
3
4
5
6
aa = pd.concat([df["LocationType"], pd.Series(["asdfasdf"])],
ignore_index = True)
aa.str.len().value_counts()
## 6 8523
## 8 1
## Name: count, dtype: int64

이렇게 .str.len() 메서드를 사용해서 규격 외의 원소를 탐지할 수 있다.

.replace()

.str.replace()는 문자열의 일부를 치환하거나 제거할 때 사용하는 메서드이다. 그리고 .replace() 메서드와 용법이 다르니 용도에 맞게 사용해야 하겠다. 다음은 “Fat” 이라는 글자를 “x”로 치환하거나 없애는 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
df["FatContent"][:4].str.replace(pat = "Fat", repl = "x")
## 0 Low x
## 1 Regular
## 2 Low x
## 3 Regular
## Name: FatContent, dtype: object

df["FatContent"][:4].str.replace(pat = "Fat", repl = "")
## 0 Low
## 1 Regular
## 2 Low
## 3 Regular
## Name: FatContent, dtype: object

“pat” 인자는 “pattern”의 준말이며 일반 문자열 패턴 또는 정규표현식(regular expression)을 사용할 수 있다. 그리고 “repl”은 “replacement”의 준말이며 “pat”인자에 선언한 패턴에 해당하는 문자열을 대체할 문자열을 써준다. 상기 코드를 보면 “repl” 인자에 비어있는 문자열을 입력하게 되면 특정 패턴의 문자열을 제거할 수 있다는 것을 알 수 있다.

정규표현식을 사용하는 경우 다양하고 복잡한 문자열 패턴을 손쉽게 바꿀 수 있다. 하지만 해당 문법을 별도로 공부해야 하기에 텍스트마이닝을 하거나 문자열 데이터를 자주 다루지 않으면 현실적으로 외우고 사용하기 어렵다. 일단 다음의 유용한 예제만 소개하고 상세한 정규표현식 사용 예제는 별도의 게시글에서 다룰 예정이다.

1
2
3
4
5
6
ser = pd.Series(["1200원", "10.23$", "1,345$"])
ser.str.replace(pat = "[^0-9.]", repl = "", regex = True)
## 0 1200
## 1 10.23
## 2 1345
## dtype: object

.contains()

.str.contains() 메서드는 특정 문자열 패턴을 만족하면 True를 반환하고 그렇지 않으면 False를 반환한다.

다음은 “at” 라는 글자가 들어간 원소에 대해 알아보는 코드이다. “Low Fat”와 “low fat”원소의 위치에 True가 있는 것을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
df["FatContent"].unique()
## array(['Low Fat', 'Regular', 'low fat', 'LF', 'reg'], dtype=object)

df["FatContent"].str.contains("at")
## 0 True
## 1 False
## 2 True
## 3 False
## 4 True
## ...
## 8518 True
## 8519 False
## 8520 True
## 8521 False
## 8522 True
## Name: FatContent, Length: 8523, dtype: bool

반환값이 TrueFalse이기 때문에 “at”가 포함되는 원소의 개수를 세는 코드는 다음과 같다.

1
2
df["FatContent"].str.contains("at").sum()
## 5201

다음은 “at” 패턴의 원소가 있는 행을 필터링 하는 코드이다. 꽤 자주 사용되는 코드 패턴이니 숙지하는 것이 좋다.

1
2
df_sub = df.loc[df["FatContent"].str.contains("at"), ]
df_sub.head()
ProductID FatContent ProductType OutletID OutletSize LocationType OutletType
0 FDA15 Low Fat Dairy OUT049 Medium Tier 1 Supermarket Type1
2 FDN15 Low Fat Meat OUT049 Medium Tier 1 Supermarket Type1
4 NCD19 Low Fat Household OUT013 High Tier 3 Supermarket Type1
7 FDP10 Low Fat Snack Foods OUT027 Medium Tier 3 Supermarket Type3
10 FDY07 Low Fat Fruits and Vegetables OUT049 Medium Tier 1 Supermarket Type1

.split()

.str.split() 메서드는 문자열 원소를 특정 패턴으로 분리하는 기능을 지원한다. 이를 확인하기 위해 다음과 같이 데이터를 준비하자.

1
2
df_pt = df[["ProductType"]].drop_duplicates().reset_index(drop = True)
df_pt
ProductType
0 Dairy
1 Soft Drinks
2 Meat
3 Fruits and Vegetables
4 Household
5 Baking Goods
6 Snack Foods
7 Frozen Foods
8 Breakfast
9 Health and Hygiene
10 Hard Drinks
11 Canned
12 Breads
13 Starchy Foods
14 Others
15 Seafood

띄어쓰기를 기준으로 분리하는 경우 다음과 같은 결과가 나오며 각 원소 내부에 리스트 형태로 원소가 배치된 것을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
df_pt["ProductType"].str.split(" ")
## 0 [Dairy]
## 1 [Soft, Drinks]
## 2 [Meat]
## 3 [Fruits, and, Vegetables]
## 4 [Household]
## 5 [Baking, Goods]
## 6 [Snack, Foods]
## 7 [Frozen, Foods]
## 8 [Breakfast]
## 9 [Health, and, Hygiene]
## 10 [Hard, Drinks]
## 11 [Canned]
## 12 [Breads]
## 13 [Starchy, Foods]
## 14 [Others]
## 15 [Seafood]
## Name: ProductType, dtype: object

길이가 제각각인 리스트가 원소에 있을 경우 그 리스트 내부의 원소로의 접근이 매우 어렵다. 그래서 리스트 구조를 풀어버리기 위해 .explode() 메서드를 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
df_pt["ProductType"].str.split(" ").explode()
## 0 Dairy
## 1 Soft
## 1 Drinks
## 2 Meat
## 3 Fruits
## 3 and
## 3 Vegetables
## 4 Household
## 5 Baking
## 5 Goods
## 6 Snack
## 6 Foods
## 7 Frozen
## 7 Foods
## 8 Breakfast
## 9 Health
## 9 and
## 9 Hygiene
## 10 Hard
## 10 Drinks
## 11 Canned
## 12 Breads
## 13 Starchy
## 13 Foods
## 14 Others
## 15 Seafood
## Name: ProductType, dtype: object

하지만 상기의 방법 밖에 없는 것은 아니다. “expand” 인자에 True를 할당하면 데이터프레임을 반환한다.

1
df_pt["ProductType"].str.split(" ", expand = True)
0 1 2
0 Dairy None None
1 Soft Drinks None
2 Meat None None
3 Fruits and Vegetables
4 Household None None
5 Baking Goods None
6 Snack Foods None
7 Frozen Foods None
8 Breakfast None None
9 Health and Hygiene
10 Hard Drinks None
11 Canned None None
12 Breads None None
13 Starchy Foods None
14 Others None None
15 Seafood None None

특정 원소에 띄어쓰기가 두 번 있는 경우 해당 원소가 3개로 분리되며 하나도 없는 경우 그대로 값이 유지된다. 반환되는 데이터프레임의 열 개수는 분리하고자 하는 패턴 일치 횟수+1 만큼 생성이 되며 상기 결과의 경우 “Fruits and Vegetables”와 “Health and Hygiene”원소 때문에 열 개수가 3개로 생성되었다. 그리고 패턴 일치 횟수가 최대값이 아닌 원소의 경우 오른쪽에 “None”이 생성되는데 이는 결측치로 취급되며 관련 메서드가 올바르게 동작하는 것을 알 수 있다.
결측치 관련 게시물 확인하기 [클릭]

1
df_pt["ProductType"].str.split(" ", expand = True).fillna("")
0 1 2
0 Dairy
1 Soft Drinks
2 Meat
3 Fruits and Vegetables
4 Household
5 Baking Goods
6 Snack Foods
7 Frozen Foods
8 Breakfast
9 Health and Hygiene
10 Hard Drinks
11 Canned
12 Breads
13 Starchy Foods
14 Others
15 Seafood

여기서 조금 불만은 반환된 데이터프레임 객체의 변수명이 단순 숫자로 되어있다는 것이다. 숫자로 된 변수명은 향후 코드 운용에서 걸림돌로 작용할 가능성이 크기 때문에 Pandas 2.0.0 버전에 새로 추가된 메서드인 .add_prefix()를 사용해서 접두사를 추가하는 것도 방법이다.

1
df_pt["ProductType"].str.split(" ", expand = True).add_prefix("type_")
type_0 type_1 type_2
0 Dairy None None
1 Soft Drinks None
2 Meat None None
3 Fruits and Vegetables
4 Household None None
5 Baking Goods None
6 Snack Foods None
7 Frozen Foods None
8 Breakfast None None
9 Health and Hygiene
10 Hard Drinks None
11 Canned None None
12 Breads None None
13 Starchy Foods None
14 Others None None
15 Seafood None None

분리 전의 데이터프레임 객체와 분리 후 데이터프레임 객체를 concat() 함수로 이어붙이는 예제는 다음과 같다.

1
pd.concat([df_pt, df_pt["ProductType"].str.split(" ", expand = True)], axis = 1)
ProductType 0 1 2
0 Dairy Dairy None None
1 Soft Drinks Soft Drinks None
2 Meat Meat None None
3 Fruits and Vegetables Fruits and Vegetables
4 Household Household None None
5 Baking Goods Baking Goods None
6 Snack Foods Snack Foods None
7 Frozen Foods Frozen Foods None
8 Breakfast Breakfast None None
9 Health and Hygiene Health and Hygiene
10 Hard Drinks Hard Drinks None
11 Canned Canned None None
12 Breads Breads None None
13 Starchy Foods Starchy Foods None
14 Others Others None None
15 Seafood Seafood None None

기타 유용한 메서드

여러 메서드가 있지만 .slice(), .extract(), .lower(), .zfill() 메서드가 나름 많이 활용되는 축에 속하니 별도의 학습을 권장한다.

Your browser is out-of-date!

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

×