일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 | 31 |
- C언어
- thymeleaf
- C++
- 파이썬
- Spring
- 알고리즘
- 프로그래머스
- 코딩테스트
- 코테
- nestJS
- 구조체배열
- python
- git
- @Autowired
- spring boot
- 스프링
- nestjs auth
- nestjs typeorm
- AWS
- 시스템호출
- TypeORM
- OpenCV
- 카카오
- 카카오 알고리즘
- Nodejs
- @Component
- 가상면접사례로배우는대규모시스템설계기초
- 컴포넌트스캔
- 해시
- 카카오 코테
- Today
- Total
공부 기록장 💻
[Python] Python for Data Analysis CH7 Data Preparation & Data Preprocessing 본문
[Python] Python for Data Analysis CH7 Data Preparation & Data Preprocessing
dream_for 2022. 11. 28. 12:19CH7. 데이터 정제 및 준비
pandas
라이브러리는 데이터를 원하는 형태로 가공하는 작업을 유연하고 빠른 고수준의 알고리즘과 처리 기능을 제공- 결측치, 중복 데이터, 문자열 처리 그리고 다른 분석 데이터 변환에 대한 도구들을 다루어보자
1. 누락된 데이터 처리하기 (Handling Missing Data)
누락 데이터를 처리하는 일은 데이터 분석 애플리케이션에서 흔히 발생하는 일이며, pandas의 설계 목표 중 하나는 누락 데이터를 가능한 한 쉽게 처리할 수 있도록 하는 것이다.
산술 데이터의 경우 pandas는 누락된 값을 쉽게 찾을 수 있도록 하기 위해 누락된 데이터를 실숫값인 NaN으로 취급한다.
분석을 위해 데이터를 정제하는 과정에서 결측치 자체를 데이터 수집 과정에서의 실수나 결측치로 인한 잠재적인 편향을 찾아내는 수단으로 인식하는 것은 매우 중요
산술 데이터에 한해 pandas는 누락 데이터를 실숫값인 NaN으로 취급한다.
numpy.nan
또는 파이썬 내장None
값을 누락값으로 취급def find_null_data(): # 누락값 np.nan은 NaN으로 표시 string_data = pd.Series(['aardvark', 'artichoke', np.nan, 'avocado']) print(string_data) print(string_data.isnull()) # 파이썬의 내장 None 값 또한 NaN으로 인식 string_data[0] = None print(string_data.isnull())
NA 처리 메서드
dropna()
: 누락된 데이터가 있는 축(로우, 컬럼)을 제외시팀킴 - 어느 정도의 누락 데이터까지 용인할 것인지 지정 가능fillna()
: 누락된 데이터를 대신할 값을 채우거나 ‘ffill’, ‘bfill’ 같은 보간 메서드를 적용isnull()
: 누락되거나 NA인 값을 알려주는 불리언 값이 저장된 같은 형의 객체를 반환notnull()
: isnull과 반대되는 메서드
누락된 데이터 골라내기
dropna()
매서드를 이용해 null이 아닌 데이터와 색인값만 들어있는 Series 반환해보자how='all'
인자는 전체가 NA 값인 로우만 제외axis=1
인자는 컬럼에 적용# dropna 메서드로 결측치 로우 제외하기 def dropna_method(): data = pd.Series([1, NA, 3.5, NA, 7]) # not null인 Series만 반환 print(data.dropna()) print(data[data.notnull()]) # 2차원 DataFrame에 dropna() 적용 data = pd.DataFrame([[1., 6.5, 3.], [1., NA, NA], [NA, NA, NA], [NA, 6.5, 3.]]) clenaed = data.dropna() # 하나라도 NA를 포함하고 있는 축은 제외 print(cleaned) # 모두 NA 값인 로우만 제외 cleaned_all_none = data.dropna(how='all') data[4] = NA print(data) # 전체가 NA인 컬럼 제외하기 cleaned_column = data.dropna(axis=1, how='all') print(cleaned_column)
시계열 데이터의 경우, 몇 개 이상의 값이 들어있는 로우만 살펴보고 싶은 경우
thresh
인자 사용하기# 값이 특정 개수 이상 들어있는 로우만 선택 def dropna_threshold(): df = pd.DataFrame(np.random.randn(7,3)) df.iloc[:4,1]=NA df.iloc[:2,2]=NA print(df) print(df.dropna()) # thresh를 2로 설정하여 2개 이상의 값이 들어있는 로우에 대해서만 DF 객체 전달 print(df.dropna(thresh=2))
결측치 채우기
dropna와 같이 누락된 값을 제외시키지 않고, 데이터 상의 구멍을 메꾸기
fillna()
메서드 사용사전 값으로 각 컬럼마다 다른 값을 채울 수도 있음
inplace=True
인자로 기존 객체를 변경 가능method='ffill'
인자로 재색인, 또는limit
인자로 최대 재색인 허용 값 지정 가능# fillna로 값 채우기 def fillna_method(): df = pd.DataFrame(np.random.randn(7,3)) print(df.fillna(0)) # 누락값을 0으로 채우기 # 사전값을 넘겨서 각 컬럼마다 다른 값을 채우기 print(df.fillna({1:0.5, 2:0})) # 1번 컬럼의 NA값에는 0.5, 2번 컬럼의 NA값에는 0 # inplace 인자로 기존 객체를 변경 _ = df.fillna(0, inplace=True) print(df) # 재색인에서 사용 가능한 보간 메서드 적용 df = pd.DataFrame(np.random.randn(6,3)) df.iloc[2:,1]=NA df.iloc[4:,2]=NA print(df) # NA 값은 컬럼의 이전 값으로 대체 print(df.fillna(method='ffill')) print(df.fillna(method='ffill', limit=2)) # 최대 2번까지 ffill 허용 # Series의 평균값 전달하여 채우기 가능 data = pd.Series([1., NA, 3.5, NA, 7]) print(data.fillna(data.mean()))
2. 데이터 변형 (Data Transformation)
중복 제거하기
df.duplicated()
: 중복된 로우 발견하기df.drop_duplicates()
: duplicated 배열이 False인 DataFrame 반환기본적으로 두 메서드는 모든 컬럼에 적용되며, 처음 발견된 값을 발견하거나 반환
keep='last'
옵션은 마지막으로 발견된 값을 반환중복 찾아내기 위한 부분합 따로 지정 가능
def duplicated_method(): data = pd.DataFrame({'k1':['one', 'two']*3 + ['two'], 'k2':[1,1,2,3,3,4,4]}) print(data) print(data.duplicated()) # 각 로우가 중복인지 아닌지 불리언 Series 반환 print(data.drop_duplicates()) # duplicated 배열이 False인 DataFrame 반환 data['v1'] = range(7) # 중복을 찾아내기 위한 부분합을 따로 지정하기 print(data.drop_duplicates(['k1'])) # k1 컬럼에 기반하여 중복 걸러내기 # a마지막으로 발견된 값을 반환 print(data.drop_duplicates(['k1', 'k2'], keep='last'))
함수나 매핑을 이용해 데이터 변형하기
map()
메서드를 이용해 데이터의 카테고리를 알려주는 칼럼 추가 가능str.lower()
: 사전류의 객체의 경우 대소문자 섞여있는 경우 소문자로 모두 변환하여 해결 가능map()
: 사전 객체의 key를 value값으로 변환
# 데이터 형태 사전을 이용해 변환하기
def data_transformation_by_mapping():
# 수집한 육류 데이터
data = pd.DataFrame({'food': ['bacon','pulled pork','bacon', 'Pastrami','corned beef',
'Bacon','pastrami','honey ham','nova lox'],
'ounces':[4,3,12,6,7.5,8,3,5,6]})
print(data)
# 육류가 어떤 동물의 고기인지 알려주는 컬럼 추가
meat_to_animal = {
'bacon':'pig',
'pulled pork':'pig',
'pastrami':'cow',
'corned beef':'cow',
'honey ham':'pig',
'nova lox':'salmon'
}
# 소문자로 변경하기 (str.lower())
lowercased = data['food'].str.lower()
print(lowercased)
# 각 육류의 고기 종류 컬럼 추가하기
data['animal'] = lowercased.map(meat_to_animal)
print(data)
# 함수 넘겨 주는 방법
data['food'].map(lambda x:meat_to_animal[x.lower()])
<br>
def main():
값 치환하기
replace()
메서드와 사전, 리스트 조합을 이용하여 치환하려는 값을 지정한 값으로 바꾸기
# 값 치환하기
de
f data_replacement():
data = pd.Series([1., -999., 2., -999., -1000., 3.])
print(data)
# 누락된 값을 나타내는 -999를 pandas에서 인식할 수 있는 NA 값으로 치환하기
data.replace(-999, np.nan)
data.replace([-999, -1000], np.nan) # 한 번에 여러 값 치환하기
data.replace([-999, -1000], [np.nan, 0]) # 치환하는 값마다 다른 값으로 치환하기
data.replace({-999: np.nan, -1000:0}) # 사전을 이용하기 가능
축 색인 이름 바꾸기
Dataframe.index.map()
을 이용해 인덱스 색인 이름 변경하기rename()
: 원래 객체를 변경하지 않고 새로운 객체를 생성하는 경우- index = str.title, columns=str.upper 과 같이 인자 옵션 지정 가능
inplace=True
옵션으로 원본 데이터 변경
# 축 색인 이름 바꾸기
def change_column_name():
data = pd.DataFrame(np.arange(12).reshape((3,4)),
index=['Ohio','Colorado','New York'],
columns=['one','two','three','four'])
transform = lambda x:x[:4].upper()
print(data.index.map(transform)) # map 이용해 축 색인 바꾸기
# 대문자로 변경된 축 이름을 바로 대입
data.index = data.index.map(transform)
print(data)
# 원래 객체 변경하지 않고 새로운 객체 생성하는 경우: rename()
print(data.rename(index=str.title, columns=str.upper))
print(data.rename(index={'OHIO':'INDIANA',},
columns={'three':'peekaboo'})) # 사전 객체를 이용해 일부 축 이름 변경
# 원본 데이터 변경 시
data.rename(index={'OHIO':'INDIANA'}, inplace=True)
print(data)
이산화(개별화)와 양자화
개별화: 연속성 데이터를 개별로 분할하거나 분석을 위해 그룹별로 나누는 경우 필요
pandas.cut(a,b)
: a리스트를 b리스트의 그룹을 기준으로 나누어 그룹화하여 Categorical 객체 반환- codes : 속한 그룹 (0부터 시작) 의 번호
- categories: 카테고리 이름
value_counts(cutted)
: Categorical 객체 cutted를 각 그룹별로 속한 데이터 개수 센 Series 반환- b 자리에 그룹 리스트가 아닌 정수 값을 넘겨주는 경우, 해당 정수만큼 균등한 길이의 그룹을 자동으로 계산
pandas.qcut()
: 표본 변위치(샘플 수)를 기반으로 데이터를 분할- cut() 의 경우 데이터의 분산에 따라 각각 그룹마다 데이터 수가 다르게 나뉘는데, qcut() 은 표준 변위치를 사용하여 적다히 같은 크기의 그룹으로 나눌 수 있음
# 그룹 분석 def discretization(): ages = [20,22,25,27,21,34,37,31,61,45,41,32] bins = [18,25,35,60,100] # 18-25, 26-35, 36-60, 60 이상의 그룹 # Catagoricals 객체 반환 : pandas의 cats는 각 ages 데이터가 속한 bins 그룹의 리스트 반환 # 중괄호 쪽의 값은 포함하지 않고 대괄호 쪽의 값을 포함 cats = pd.cut(ages,bins) print(cats) print(cats.codes) print(cats.categories) # 카테고리의 이름 print(pd.value_counts(cats)) # 각 카테고리에 속한 데이터의 개수 반환 # 중괄호 대신 대괄호 쪽이 포함되지 않도록 변경 print(pd.cut(ages, [18,26,36,61,100], right=False)) # labels 옵션으로 그룹의 이름 지정하기 group_names = ['Youth','YoungAdult','MiddleAged', 'Senior'] print(pd.cut(ages, bins, labels=group_names))
# 그룹의 경계값이 아닌 그룹의 개수를 넘겨주면 데이터의 최솟값, 최댓값을 기준으로 균등한 길이의 그룹을 자동 계산
data = np.random.randn(20)
print(pd.cut(data, 4, precision=2)) # 균등분포 내에서 4개의 그룹으로 나누는 경우 (precision: 소수점 아래 2자리로 제한)
# qcut: 표본 변위치를 기반으로 데이터 분할
data = np.random.randn(1000)
cats = pd.qcut(data, 4) # 4분위로 분류
print(cats)
print(pd.value_counts(cats)) # 각 그룹의 개수 250개
# 변위치를 직접 지정 (0부터 1 사이)
print(pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.]))
```
특잇값을 찾고 제외하기
배열 연산 시 특잇값(outlier)을 제외하거나 적당한 값으로 대체하는 것이 중요
np.sign(data)
: 양, 음수에 따라 1과 -1 반환# 특잇값 outlier 제외하거나 대체하기 def handle_outlier(): data = pd.DataFrame(np.random.randn(1000,4)) # 4개의 컬럼에 각각 1000개의 실수 데이터 생성 print(data.describe()) # 한 컬럼에서 절댓값이 3을 초과하는 데이터 탐색 col = data[2] print(col[np.abs(col)>3]) print(data[(np.abs(data)>3)].any()) # 절댓값 3 초과하는 값이 들어있는 모든 로우 선택시 data[np.abs(data)>3] = np.sign(data)*3 # 초과하는 값을 3, -3으로 변경 print(data.describe()) print(np.sign(data).head()) # 양수, 음수에 따라 1이나 -1 담긴 배열을 반환
치환과 임의 샘플링
np.random.permutation(n)
: 0부터 (n-1)까지의 값을 랜덤으로 재배치DataFrame.take(sampler)
: sampler를 이용해 데이터프레임의 축 순서를 변경DataFrame.sample(n=)
: 치환없이 임의로 n개의 축 선택하기replace=True
옵션으로 반복 허용# 치환과 임의 샘플링 def permutatation_sampling_replacement(): df = pd.DataFrame(np.arange(5*4).reshape((5,4))) print(df) sampler = np.random.permutation(5) # 0부터 4까지 숫자를 임의로 재배치 print(sampler) # sampler에 의해 take() 메서드로 배치 변경 print(df.take(sampler)) # sample()로 치환 없이 일부만 임의로 선택 print(df.sample(n=3)) # 3개의 랜덤 로우 선택 # 반복 선택을 허용하여 표본을 치환을 통해 생성해 내기 (replace=True 옵션 지정) choices = pd.Series([5,7,-1,6,4]) draws = choices.sample(n=10, replace=True) # choices 배열의 값으로 반복 허용하여 10개의 샘플 생성 print(draws)
표시자/더미 변수 계산하기
분류값을 ‘더미’나 ‘표시자’ 행렬로 전환하여 데이터를 변환해보자.
pandas.get_dummies()
: 어떤 DataFrame의 한 컬럼에 k가지의 값이 있는 경우 k개의 컬럼이 있는 DataFrame이나 행렬을 만들고, 값으로는 1과 0으로 채워넣기prefix
인자 옵션 → 접두어 추가 가능join()
: 다른 데이터와 병합하기# 표시자/더미 변수 계산하기 def dummies(): df = pd.DataFrame({'key':['b','b','a','c','a','b'], 'data1':range(6)}) print(pd.get_dummies(df['key'])) # key의 값이 컬럼이 되고, 해당되는 값에 1, 아닌 경우 0을 지정 # 컬럼에 접두어(prefix) 추가하고 다른 데이터와 병합하기 dummies = pd.get_dummies(df['key'], prefix='key') # 컬럼이 key_a, key_b, key_c로 변경 df_with_dummy = df[['data1']].join(dummies) # data1과 dummies 병합 print(df_with_dummy)
영화 장르 데이터 다루기
# movie 데이터의 genre를 카테고리로 개별화하기
def movies_with_categories():
mnames = ['movie_id','title','genres']
movies = pd.read_table('datasets/movielens/movies.dat',sep='::', header=None, names=mnames,
engine='python', encoding='ISO-8859-1')
print(movies[:10]) # 컬럼: movie_id, title, genres
# 각 장르마다 표시자 값 추가하기
all_genres = [] # 모든 장르 리스트
for x in movies.genres:
all_genres.extend(x.split('|'))
genres = pd.unique(all_genres)
print(genres)
# 표시자 DataFrame 생성
zero_matrix = np.zeros((len(movies), len(genres)))
dummies = pd.DataFrame(zero_matrix, columns=genres)
# 각 영화 순회하며 dummies의 각 로우 항목을 1로 설정
gen = movies.genres[0]
print(gen.split('|'))
dummies.columns.get_indexer(gen.split('|'))
for i, gen in enumerate(movies.genres):
indices = dummies.columns.get_indexer(gen.split('|'))
dummies.iloc[i, indices] = 1
# movies와 조합하기
movies_windic = movies.join(dummies.add_prefix('Genre_')) # Prefix 지정
print(movies_windic.iloc[0])
3. 문자열 다루기
문자열 객체의 내장 메서드로 문자열 텍스트 처리 가능
복잡한 패턴 매칭이나 텍스트 조작 → 정규 표현식 이용
pandas는 배열 데이터 전체에 쉽게 정규 표현식으로 적용하고, 누락된 데이터를 편리하게 처리할 수 있는 기능을 포함하고 있음
문자열 객체 메서드
split(',')
: 특정 문자를 기준으로 문자열 자르기strip()
: 앞뒤의 공백 문자 제거':'join()
: 리스트를 어떠한 문자열로 연결하여 합치기index(',')
: 문자열에서 특정 문자열 찾기 (찾지 못한 경우 예외 처리)find(',')
: 문자열에서 특정 문자열 찾기 (찾지 못한 경우 -1 반환)count(',')
: 특정 문자열의 발견 횟수 반환replace('','')
: 찾아낸 패턴을 다른 문자열로 치환
# 문자열 객체 메서드
def string_method():
# split() - 특정 문자를 기준으로 문자열 자르기
val = 'a,b, guido'
print(val.split(',')) # 리스트 반환
# strip() - 공백 문자 제거
pieces = [x.strip() for x in val.split(',')] # 공백문자 제거한 문자열의 리스트
print(pieces)
# ''.join() - 리스트를 문자열로 합치기
print('::'.join(pieces))
# 문자열 내 특정 문자열의 위치 찾기
print('guido' in val)
print(val.index(',')) # 처음으로 찾은 문자의 위치 찾기
# print(val.index(':')) # 문자 찾지 못한 경우 예외 처리 발생
print(val.find(':')) # 문자 찾지 못한 경우 -1 반환
# count() - 특정 부분 문자열 발견 횟수 반환
print(val.count(','))
# replace() - 찾아낸 패턴을 다른 문자열로 치환
print(val.replace(',', '::'))
정규 표현식
정규 표현식 : 텍스트에서 문자열 패턴을 찾는 유연한 방법을 제공
regex 단일 표현식: 정규 표현 언어로 구성된 문자열 (파이썬의
re
모듈 내장)re.split()
: 정규 표현식 컴파일 후 split메서드 실행re.complie()
: 직접 정규 표현식을 컴파일하여 정규 표현식 얻기re.findall()
: 정규 표현식에 매칭되는 모든 패턴의 목록 얻기# 정규 표현식 활용하기 def regular_expression(): import re #하나 이상의 공백 문자를 의미하는 \s+를 사용하여 문자열 분리 text = "foo bar\t baz \tqux" print(re.split('\s+', text)) # 정규 표현식 컴파일 후 split 메서드 실행 # 직접 정규 표현식을 컴파일하여 얻은 정규 표현식 객체를 재사용하기 regex = re.compile('\s+') print(regex.split(text)) # 정규 표현식에 매칭되는 모든 패턴의 목록 얻기 print(regex.findall(text))
findall()
: 문자열에서 일치하는 모든 부분 문자열 찾기search()
: 패턴과 일치하는 첫번째 존재 반환match()
: 문자열의 시작 부분에서 일치하는 것만 찾음# 이메일 예제로 정규 표현식 이해하기 def regular_expression_email(): import re text = """Dave dave@google.com Steve steve@gmail.com Rob rob@gmail.com Ryan ryan@yahoo.com """ pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}' regex = re.compile(pattern, flags=re.IGNORECASE) # 대소문자 구분 안하기 print(regex.findall(text)) # pattern에 해당하는 부분 문자열 리스트 # search: 패턴과 일치하는 첫 번째 이메일 주소만 찾기 m = regex.search(text) print(m) # 정규 표현 패턴이 위치하는 시작점, 끝점 반환 print(text[m.start():m.end()]) # match: 문자열의 시작점에서부터 일치하는지 검사 print(regex.match(text)) # sub: 주어진 문자열로 치환 print(regex.sub('REDACTED', text)) # 사용자 이름, 도메인 이름, 도메인 접미사 3가지 컴포넌트로 나누기 pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})' regex = re.compile(pattern, flags=re.IGNORECASE) # match: groups메서드로 각 패턴 컴포넌트의 튜플 얻기 m = regex.match('wesm@bright.net') print(m.groups()) print(regex.findall(text)) # \1, \2 같은 특수 기호로 각 패턴 그룹에 접근 print(regex.sub(r'Username: \1 Doimain: \2, Suffix: \3', text))
pandas의 벡터화된 문자열 함수
- 문자열과 정규 표현식 메서드는
[data.map](http://data.map)
을 사용해 각 값에 적용할 수 있지만 NA 값을 만나면 실패함 - 이를 대처하기 위해 Series에서 str 속성을 이용해 NA 값을 건너뛰도록 한다.
str.contains()
: 어떤 문자열을 포함하고 있는지 검사str.get()
,str[0]
와 같이 색인을 이용해 벡터화된 요소를 꺼내오기
# 벡터화된 문자열 함수
def vectorized_string_method():
# 누락된 값을 포함하는 데이터
data = {'Dave':'dave@google.com',
'Steve':'steve@gamil.com',
'Rob':'rob@gmail.com',
'Wes':np.nan}
data = pd.Series(data)
print(data)
print(data.isnull())
# NA 값을 넘어뛰도록 문자열의 gmail 포함 여부 확인
print(data.str.contains('gmail'))
pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
print(data.str.findall(pattern, flags=re.IGNORECASE))
matches = data.str.match(pattern, flags=re.IGNORECASE)
print(matches)
# print(matches.str.get(1))
# print(matches.str[0])
print(data.str[:5])