반응형
250x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- HTML
- 메소드
- Seaborn
- 오늘도코드잇
- 코딩
- 코드잇TIL
- CSS
- 나혼자코딩
- 코드잇 TIL
- 경사하강법
- for반복문
- 코드잇
- sql연습문제
- 다항회귀
- 서브쿼리
- 로지스틱회귀
- 선형회귀
- pandas
- 코딩공부
- 윈도우함수
- 머신러닝
- numpy
- 코딩독학
- matplotlib
- 결정트리
- 데이터분석
- 행렬
- 판다스
- 파이썬
- SQL
Archives
- Today
- Total
Coding Diary.
(데이터분석) 파이썬으로 데이터 정제하기 본문
728x90
반응형
데이터 품질을 점검할 때는 일반적으로 완전성 문제를 먼저 처리하는 것이 좋습니다. 그러면 이후에 누락 데이터로 인한 정제 과정을 반복할 필요가 없기 때문입니다.
* 결측 데이터 및 데이터 정돈 문제
문제 1) 하나의 열에 여러 개의 변수 존재
- 문자열 처리 및 unpivoting을 통해 해결합니다.
(step 1) 결측 데이터 처리하기 (Clean Missing Data)
#데이터 불러오기
import pandas as pd
import numpy as np
patients = pd.read_csv('patients.csv')
treatments = pd.read_csv('treatment.csv')
adverse_reactions = pd.read_csv('adverse_reaction.csv')
#결측값 존재 확인 및 데이터 복제
treatments_clean = treatments.copy()
treatments.info()
- 350이 아닌 280 레코드가 존재한다고 나와져 있습니다. => 결측값
- 이전 단계로 돌아가서 데이터를 제대로 수집했는데 점검해 보고 이 경우에는 데이터를 추가로 수집해서 결측값 문제 해결해야 합니다.
- 결측값을 모두 찾아서 treatment_cut.csv라는 별도의 파일에 저장했다고 가정합시다.
treatments_cut = pd.read_csv('treatments_cut.csv')
treatments_cut.info()
- 총 70개의 엔트리 나옵니다.
- 이 엔트리를 원래의 treatments DaraFrame에 추가해야 합니다.
- .concat() 함수를 이용합니다. => treatmentscut.csv파일의 열이 원래의 treatments DataFrame 끝에 추가로 연결됩니다.
- 이때 treatments_clean은 treatments DaraFrame 의 사본입니다.
treatments_clean = pd.concat([treatments_clean, treatments_cut], ignore_index=True)
treatments_clean.head()
treatments_clean.info()
- 마지막 열인 hba1c_change를 제외한 열의 엔트리가 모두 350개라는 걸 알 수 있습니다.
- 이제 hba1c_change 열에 존재하는 결측값을 처리해봅시다.
- hba1c_change에 해당하는 값은 hba1c_start 열의 값에서 hba1c_end 열의 값을 뺀 값입니다.
- 따라서 이 경우에는 start열과 end 열에 모두 값이 존재하므로 결측값을 대체할 필요가 없습니다.
- 따라서 start-end로 구성된 새로운 열로 원래의 hba1c_change 열을 대체합시다.
treatments_clean.hba1c_change = (treatments_clean.hba1c_start - treatments_clean.hba1c_end)
treatments_clean.hba2c_change.head()
treatments_clean.info()
treatments_clean.hba1c_change.sample()
- 모든 열에서 엔트리가 350개가 되었으며 .sample메서드를 실행해도 결과가 정상적으로 반환됩니다.
(step 2) 데이터 비정돈 문제 (Clean Tidiness Issues)
[Issue 1: Contact column in patients table]
- 이제 이 데이트세트에서 찾아낸 비정돈 문제를 해결해봅시다.
- patient 테이블의 contact 열에는 전화번호와 이메일이 모두 입력되어 있습니다.
- 이것은 두 개의 열로 분리해야 합니다. (by 문제1)
- 또한, 전화번호가 이메일 주소 앞에 올 때도 있고 그 반대일 때도 있습니다.
patients.head()
- patients DaraFrame의 복사본을 만들어 patients_clean 변수에 할당합니다.
patients_clean = patients.copy()
- 그 다음, 정규 표현식과 pandas의 str.extract 메서드를 사용해 contact 열로부터 전화번호와 이메일 변수를 추출합니다.
- 그런 다음, 기존의 contact 열은 삭제합니다.
#Extract the phone number
#expand=True : 분할된 문자열을 별개의 열로 나눔
#아래의 정규 표현식은 이메일 앞,뒤에 있는 모든 전화번호를 찾아낼 수 있도록 구성됨
patients_clean['phone_number'] = patients_clean.contact.str.extract(
'((?:\+\d{1,2}\s)?\(?d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4})]', expand=True)
#[a-zA-Z] to signify emails in this dataset all start and end with letters
patients_clean['email'] = patients_clean.contact.str.extract('([e-aA-Z][a-zA-Z0-9_.+2]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+[a-zA-Z])', expand=True)
#aixs=1 denotes that we are referring to a column, not a row
patients_clean = patients_clean_drop.('contact', axis=1)
- phone_number 과 email이 분리되었습니다.
patients_clean.head()
- sample로 몇개를 추출해서 뽑아보면 전화번호와 이메일 주소가 명확히 분리되었다는 것을 알 수 있습니다.
patients_clean.phone_number.sample(25)
- 한편, .sort_values 로는 정수로 시작하는 이메일이 없다는 걸 확인할 수 있습니다.
patients_clean.email.sort_values().head()
[Issue 2: Three variables in two columns in treatments table]
- 이제, 다른 비정돈 데이터 문제를 보겠습니다.
treatment_clean.head()
- treatment 테이블의 Auralin과 Novodra 열에는 세 개의 변수가 존재합니다. 각각 투여한 약물, 시작 투여량, 종료 투여량 입니다.
- 이 문제는 unpivoting으로 해결할 수 있습니다.
- 먼저, .melt로 unpivoting을 하겠습니다.
treatments_clean = pd.melt(treatments_clean, id_vars =
['given_name', 'surname', 'hba1c_start', 'hba1c_end', 'hba1c_change'],
#모두 그대로 유지해야하는 열
var_name='treatment', value_name='dose')
#변수 이름을 treatment로, 값의 이름을 dose로 설정
treatments_clean.head()
- 이에 따라 auralin과 novodra 열의 헤더는 treatment 열로 변환되었으며 그 값은 환자에게 투여된 약물의 이름을 나타냅니다.
- 이제 dose 열을 분리해서 정수로 구성된 시작 투여량과 종료 투여량으로 나눠야 합니다.
- 먼저, treatments_clean DataFrame에서 dose 열을 선택하고 .str.split으로 시작 투여량과 종류 투여량을 분리합니다.
- 하이픈을 기준으로 분리하면 됩니다.
- 첫 번째 인수에 하이픈을 입력해서 이를 기준으로 값을 분할하게 됩니다.
treatments_clean = treatments_clean[treatments_clean.dose != "-"]
treatments_clean
treatments_clean[['dose_start', 'dose_end']] = treatments_clean['dose'].str.split(' - ', n=1, expand=True)
#n=1은 첫 번째로 등장한 구분 기호만 분할의 기준이 되어야 한다는 뜻
#expand=True는 분리한 값이 서로 다른 열을 형성하게 함. 따라서 DaraFrame 끝에 두 개의 열이 추가 됨
treatments_clean = treatments_clean.drop('dose', axis=1)
treatments_clean.head()
[Issue 3: Adverse reaction should be part of the treatments table]
- 다음 비정돈 문제를 살펴보겠습니다.
- adverse_reactions 테이블은 treatments 테이블에 속해야 합니다. 둘의 관찰 단위가 개별 환자로서 동일하기 때문입니다.
- adverse_reactions 열을 treatment 테이블에 병합하고 키는 given name과 surname으로 지정합니다.
- 이 때, 매개변수 how를 left로 설정합니다. treatments_clean 행은 최종 DataFrame에 모두 포함되지만 오른쪽 DaraFrame인 adverse_reactions_clean에서는 키가 일치하는 행만 포함됩니다.
adverse_reactions_clean = adverse_reactions.copy()
treatments_clean = pd.merge(treatments_clean, adverse_reactions_clean,
on=['given_name', 'surname'], how='left']
treatments_clean
[Issue 4: Duplicated given name and surname in different tables]
- 다음 비정돈 문제로 넘어가보겠습니다.
- patients 테이블의 given_name과 surname 열은 treatments 테이블에도 중복으로 존재합니다.
patients_clean.head()
treatments_clean.head()
- 하지만 treatments 테이블에는 이름과 성이 소문자로 입력되어 있습니다.
- 목표는 treatments_clean에서 이름과 성을 나타내는 열을 삭제해서 하나의 patient_id 열로 대체하고 환자의 이름과 성은 patients_clean에만 남겨서 각 관찰 단위를 깔끔하게 정리하는 것입니다.
- 현재 treatments_clean 테이블에는 patient_id 열이 없습니다.
- 먼저, patients_clean DaraFrame 으로부터 patient_id, given_name, surname열을 가져와 세 개의 열을 복사해서 id_names라는 변수에 할당하고 str.lower로 given_name과 surname 열의 값을 소문자로 변환합니다.
- 그 다음, pandas merge 함수를 사용해서 treatments_clean DaraFrame 과 방금 만든 id_names 변수를 병합합니다.
- 이때 키는 given_name과 surname열로 지정합니다.
- 마지막으로, given_name과 surname 열을 삭제합니다.
id_names = patients_clean[['patient_id', 'given_name', 'surname']].copy()
id_names['given_name'] = id_names['given_name'].str.lower()
id_names['surname'] = id_names['surname'].str.lower()
treatments_clean = pd.merge(treatments_clean, id_names, on=['given_name', 'surname'])
treatments_clean = treatments_clean.drop(['given_name', 'surname'], axis=1)
treatments_clean.head()
patients_clean.head()
- 이제, treatments_clean 테이블에 given_name과 surname이 사라지고 patient_id 만 남고, patients_clean 테이블에 환자 이름과 성이 남습니다.
- 이렇게 환자에 대한 정보는 patients_clean 테이블 하나에, 약물에 대한 정보는 treatments_clean 테이블 하나에 모두 정리되었고 patient_id로 두 테이블을 연결할 수 있습니다.
문제 2) 하나의 관찰 단위가 여러 테이블에 흩어져 있는 경우
- merging으로 해결합니다.
(step 3) 타당성 문제 (Clean Quality Issues)
[Issue 1 Validity : Erroneous data types]
- 투여량이 두 자리의 정수에 단위를 나타내는 u가 덧붙여진 형식으로 입력되어 있습니다.
- 두 열의 값을 용이하게 처리하기 위해서는 두 열의 값이 정수 형식이 되어야 합니다.
- 따라서 각 열에서 u라는 문자를 제거해야 합니다.
treatments_clean[["dose_start", "dose_end"]].head()
- 먼저, str.strip 메서드로 u를 제거한 다음 .astype(int)로 그 값이 정수 형식이 되게 설정합니다.
- 그 다음, dose_start와 dose_end의 데이터 유형이 모두 정수인지를 확인합니다.
treatments_clean.dose_start = treatments_clean.dose_start.str.strip('u').astype(int)
treatments_celan.dose_end = treatments_clean.dose_end.str.strip('u').astype(int)
treatments_clean[['dose_strat', 'dose_end']].dtypes
- 또한 파이썬의 assert 문을 사용하여 프로그래밍 방식으로 데이터 유형이 올바른지 확인할 수도 있습니다.
assert treatments_clean[['dose_start', 'dose_end']].dtypes[0] == 'int64'
assert treatmenst_clean[['dose_start', 'dose_end']].dtypes[1] == 'int64'
- 그 다음 문제로, patients 테이블에서 zip_code는 문자열이 아니라 float입니다.
patients_clean[["zip_code"]].head()
- 이렇게 데이터 유형이 잘못 설정되면 이후에 문제가 발생할 수 있습니다. 우편번호는 숫자로 취급할 수 있는 값이 아니기 때문입니다.
- 또한, 네자리로 설정된 7095.0 은 07095인 5자리가 되어야 합니다.
- 따라서, astype(str)로 zip_code열의 데이터 유형을 float에서 문자열로 반환합니다.
#str[:-2]로 .0부분을 잘라내고 pad로 모자라는 자릿수만큼 앞에다 0을 채워넣습니다.
patients_clean.zip_code = patients_clean.zip_code.astype(str).str[:-2].str.pad(5, fillchar='0')
#하지만 이렇게 하면 결측값이 '0000n'으로 반환되므로 이를 다시 np.nan으로 교체해야 합니다.
patients_clean.zip_code = patients_clean.zip_code.replace('0000n', np.nan)
patients_clean[["zip_code"]].head()
- 한편, patients 테이블에는 assigned_sex, state, birthdate 열의 데이터 유형이 잘못 설정되어 있습니다.
patients_clean.dtypes
- assigned_sex 와 state 변수는 astype 메서드를 사용해 범주형으로 지정할 수 있으며 birthdate 변수는 pandas datetime 객체로 변환할 수 있습니다.
patients_clean.assigned_sex = patients_clean.assigned_sex.astype('category')
patients_clean.state = patients_clean.state.astype('category')
patients_clean.bithdate = pd.to_datetime(patients_clean.birthdate)
patients_clean.dtypes
[Issue 2 : Accuracy]
- 이번에는 patients 테이블의 정확성 문제를 확인해 보겠습니다.
- 참고로 patients 테이블에서 height 열에 키가 72가 27로 잘못되어 있습니다.
- 먼저, patients 테이블에서 height 열에 27이 입력되어 있는 행을 찾고 .replace를 사용해서 그 값을 72로 바꿉니다.
- 유사한 문제가 있는지 확인하기 위해 데이터세트에 height가 30미만인 경우가 있는 지를 점검합니다.
- 인덱싱으로 확인해보면 문제가 없다는 걸 알 수 있습니다.
patients_clean.height = patients_clean.height.replace(27, 72)
patients_clean[patients_clean.height <= 30]
- 또한, Tim Neudorf의 surname으로 인덱싱을 해서 해당 레코드를 직접 확인해 봅니다.
patients_clean[patients_clean.surname == 'Neudorf']
- 또한, David의 이름이 Dsvid로 잘못 입력된 경우가 존재합니다.
- 마찬가지로 replace 메서드를 사용하여 정정합니다.
patients_clean.given_name = patients_clean.given_name.replace('Dsvid', 'David')
patients_clean[patients_clean.surname == 'Gustafsson']
[Issue 3 : Consistency 일관성]
- 일관성 문제를 살펴보겠습니다.
- 환자 한 명의 체중이 킬로그램 단위에 따라 48.8로 입력되어있는데 이를 파운드 단위로 바꾸어야 합니다.
- 먼저, 인덱싱 마스크를 설정합니다.
- 평가 단계에서 해당 환자 이름이 Zaitseva 라는 것을 확인했으므로 이를 활용한 mask로 해당 환자의 체중을 가져옵니다.
mask = patients_clean.surname == 'Zaitseva'
weight_kg = patients_clean[mask].weight
weight_kg
- 이어서 .loc으로 해당 환자의 열에 액세스하고 weight열에 킬로그램 단위의 값을 파운드 단위로 변환할 때 곱하는 환산 계수를 곱해서 그 값을 파운드 단위로 변환합니다.
patients_clean.loc[mask, 'weight'] = weight_kg * 2.20462
- sort_values로 확인해 보면 최솟값이 48.8이 아님을 알 수 있습니다.
patients_clean.weight.sort_values()
- 또한 파이썬의 assert을 활용하여 weight 열의 최소값이 100 이상인지를 확인할 수도 있습니다
assert patients_clean.weight.min() >= 100
[Issue 4 : Uniqueness + Validity (고유성+타당성)]
- patients DaraFrame의 고유성 및 타당성을 살펴보겠습니다.
- 성이 Doe이고 주소가 123 Main Street인 중복값이 6개 존재함을 이전에 확인했습니다.
John_Doe = patients_clean[(patients_clean.surname == 'Doe') & (patients_clean.given_name == 'John')]
John_Doe
- 이 값을 삭제해야한다고 합시다. 이는 surname이 Doe, given_name이 John인 행을 삭제해야 합니다.
- 조건에 알맞게 인덱싱 한 후 같지 않음 !=을 이용해서 환자 이름이 John Doe 인 열을 모두 삭제합니다.
patients_clean = patients_clean[(patients_clean.surname != 'Doe') & (patients_clean.given_name != 'John')]
- value_counts를 이용하여 확인해보면 John Doe라는 이름이 나타나지 않는 것을 확인할 수 있습니다.
patients_clean.surname.value_counts()
- 한편, address 변수에 대해 value_counts를 실행해도 Jogn Doe의 주소가 존재하지 않는다는 것을 확인할 수 있습니다.
patients_clean.address.value_counts()
- 이제 마지막 문제를 살펴보겠습니다.
- patients 테이블에는 환자의 별명을 포함하는 레코드가 일부 존재합니다.
patients_clean[patients_clean.surname == 'Jakobsen']
patients_clean[patients_clean.surname == 'Gersten']
patients_clean[patients_clean.surname == 'Taylor']
- Jake Jakob, Pat Patrick, Sandy Sandra은 각각 같은 주소를 공유하는 동일인물 이기 때문에 문제가 됩니다.
- 이 별명은 treatments 테이블에는 존재하지 않으므로 중복 행을 제거할 때 주의를 기울여야 합니다.
- 이름을 잘못 삭제하면 patients 테이블과 treatments 테이블 사이에 일관성 문제가 발생하기 때문입니다.
- 잘 살펴보면, 환자의 별명이 입력된 레코드는 모두 두 번째 중복 행입니다.
- 이는 한편 유일하게 null이 아니면서 중복값인 주소들이 입력된 행이기도 합니다.
- 따라서 별명인 행을 찾아내기 위해 null이 아니면서 중복인 주소가 입력된 행을 모두 찾아냅시다.
- 먼저, address 열에 duplicated 메서드를 사용해서 주소가 중복값인 행을 정의합니다.
- 이어서 notnull 메서드로 주소가 null이 아닌 행을 정의합니다.
- 그리고 & 연산자를 사용해서 두 메서드를 연결함으로써 중복인 주소와 null이 아닌 주소라는 두 조건을 모두 만족하는 결과가 반환되게 합니다.
patients_clean[((patients_clean.address.duplicated()) & patients_clean.address.notnull())]
- 이제 이 열들을 제거합니다. 이때 조건의 역을 나타내는 ~ 연산자를 사용해서 null이 안면서 중복값인 주소가 존재하는 행을 모두 배제합니다.
- 그리고 그 결과를 patients_clean DaraFrame에 할당합니다.
patients_clean = patients_clean[~((patients_clean.address.duplicated()) & patients_clean.address.notnull())]
- 확인해보면 별명으로 중복된 행들이 다 사라진 걸 알수 있습니다.
patients_clean[patients_clean.surname == 'Jakobsen']
patients_clean[patients_clean.surname == 'Gersten']
patients_clean[patients_clean.surname == 'Taylor']
728x90
반응형
'Coding > Data analysis' 카테고리의 다른 글
(데이터분석) 파이썬으로 텍스트 데이터 정제 함수 만들기 (0) | 2024.05.20 |
---|---|
(데이터분석) Scikit-Learn 사이킷런으로 데이터 전처리하기 (0) | 2024.05.20 |
(데이터분석) 결측 데이터 처리하기 (0) | 2024.05.17 |
(데이터분석) 중복된 값 제거하기 (0) | 2024.05.17 |
(데이터분석) 웹 페이지 스크래핑 연습문제 (0) | 2024.05.13 |