260108 [ Day 8 ] - Data (2)

TaeHyun·2026년 1월 8일

TIL

목록 보기
131/184

시작하며

오늘은 데이터 전처리에 대해서 배우고 현업에서는 어떤 방식으로 전처리를 진행하고 편하게 할 수 있는 팁들을 배워보았다.

데이터 전처리

  • 데이터 품질이 머신러닝 알고리즘 간의 차이보다 분석 결과에 훨씬 큰 영향을 미침
  • 데이터를 수집하고 정리하는 업무가 전체 과정의 약 80% 차지
  • 결측값과 이상치를 탐지하고 처리
    • 결측값(missing) : 공간은 있지만 값이 없는 것
    • 이상치(outlier) : 중심에서 멀리 떨어진 값
  • 올바른 자료형으로 변환
  • 다양한 파생변수를 생성

열이름으로 열 선택

apt['지역코드'] # 생략
apt.loc[:, '지역코드'] # 원형
  • 팬시인덱싱 : 리스트 원소의 순서대로 열 정렬
apt[['지역코드', '거래금액']] # 생략
apt.loc[:, ['지역코드', '거래금액']] # 원형
apt.loc[:, ['거래금액', '지역코드', '아파트']]
apt.loc[:, '거래금액':'아파트']

조건에 맞는 열 선택

특정 타입 컬럼만 선택

  • dtypes 사용
apt.loc[:, apt.dtypes == 'int64']
apt.loc[:, (apt.dtypes == 'int64') | (apt.dtypes == 'float64')]
  • select_dtypes 사용
    • exclude : 필요 없는 클래스 지정 가능
apt.select_dtypes(include=[int, float])

특정 문자열 포함 컬럼만 선택

  • str.contains 사용
    • 파이프(|)는 정규 표현식에서 or를 의미하는 메타문자
apt.loc[:, apt.columns.str.contains(pat='년|월|일')]

문자열 시리즈의 str 접근자

  • sr.str[index] : 각 원소를 대괄호 안에 지정한 인덱스로 인덱싱
    • 대괄호 안에 슬라이스를 지정하면 각 원소를 슬라이싱함
  • sr.str.split(pat) : 각 원소를 패턴으로 분리한 리스트를 시리즈로 반환
  • sr.str.contains(pat) : 각 원소에서 패턴의 존재 여부를 불리언 시리즈로 반환
  • sr.str.replace(pat, repl) : 패턴에 맞는 문자열을 repl에 지정한 문자열로 변경
    • 정규 표현식일 때 regex 매개변수에 True 지정
  • sr.str.extract(pat) : 패턴에 맞는 첫 번째 문자열을 데이터프레임으로 반환
    • 캡처링 그룹을 추출하므로 반드시 소괄호를 사용
  • sr.str.extractall(pat) : 패턴에 맞는 모든 문자열을 데이터프레임으로 반환

정규 표현식 기본 문법

구분상세 내용구분상세 내용
‘.’줄바꿈을 제외한 모든 문자(공백 포함)‘[a-z]’알파벳 소문자(26개)
‘\\w’알파벳 대소문자, 숫자, 언더바, 한글‘[A-Z]’알파벳 대문자(26개)
‘\\d’숫자‘[0-9]’숫자(10개)
‘\\s’공백, ‘\r\n’ (줄바꿈), ‘\t’ (탭)‘[ㄱ-ㅎ]’한글 단자음, 쌍자음, 겹자음(30개)
‘\\W’‘\\w’ 의 반대‘[ㅏ-ㅣ]’한글 단모음, 이중모음(21개)
‘\\D’‘\\d’ 의 반대‘[가-힣]’한글 완성형(11,172개)
‘\\S’‘\\s’ 의 반대‘[一-龥]’한자(20,902개)
`‘’`문자열을 or로 지정‘[^가-힣]’
‘+’앞 패턴이 1~∞ 연속 일치‘\\b’단어의 경계 여부 검사(단어 경계)
‘*’앞 패턴이 0~∞ 연속 일치‘\\B’‘\\b' 의 반대(비단어 경계)
‘?’앞 패턴이 0 또는 1번 일치‘()’일치하는 패턴 저장(캡처링 그룹)
‘{n}’앞 패턴이 정확하게 n번 연속 일치‘(?:)’패턴 저장하지 않음(비캡처링 그룹)
‘{n,m}’앞 패턴이 n~m번 연속 일치(n < m)‘\\n’n번째 캡처링 그룹 재탐색(역참조)
‘{n,}’앞 패턴이 n~∞ 연속 일치‘A(?=B)’B 앞에 오는 A 패턴 매칭(긍정 전방탐색)
‘\’메타문자의 기능 해제(이스케이프)‘(?≤A)B’A 뒤에 오는 B 패턴 매칭(긍정 후방탐색)
‘^’문자열의 시작 위치 고정(앵커)‘A(?!B)’?= 의 반대(부정 전방탐색)
‘$’문자열의 끝 위치 고정(앵커)‘(?<!A)B’?<= 의 반대(부정 후방탐색)

문자열 시리즈 처리(str 접근자 + 정규표현식)

nums = pd.Series(data=['M:010-2345-6789', 'T:02-345-6789', 'ID:001231-7890123'])

nums.str[0]
# 0    M
# 1    T
# 2    I
# dtype: object

nums.str[0:2]
# 0    M:
# 1    T:
# 2    ID
# dtype: object
nums.str.extract(pat='(M|T|ID)')
nums.str.extract(pat='([A-Z]+)')
#   0
# 0	M
# 1	T
# 2	ID
nums.str.split(pat=':')
# 0      [M, 010-2345-6789]
# 1        [T, 02-345-6789]
# 2    [ID, 001231-7890123]
# dtype: object
nums.str.split(pat=':|-')
# 0     [M, 010, 2345, 6789]
# 1       [T, 02, 345, 6789]
# 2    [ID, 001231, 7890123]
# dtype: object

nums.str.split(pat=':|-', expand=True)
#   0	  1	      2	      3
# 0	M	  010	    2345	  6789
# 1	T	  02	    345	    6789
# 2	ID	001231	7890123	None
  • str.extract는 실행 결과로 항상 데이터프레임을 반환
nums.str.extract(pat='([0-9]+)')
	# 0
# 0	010
# 1	02
# 2	001231
  • 전화번호 가운데만 추출
nums.str.extract(pat='(-[0-9]+-)')[0].str.replace(pat='-', repl='')
# 0    2345
# 1     345
# 2     NaN
# Name: 0, dtype: object

nums.str.extract(pat='((?<=-)[0-9]+(?=-))')[0]

조건에 맞는 행 선택(연속형)

cond = apt['거래금액'] >= 1500000
apt.loc[cond, :]
cond = (apt['거래금액'] <= 1500000) & (apt['층'] >= 65)
apt.loc[cond, :]

시리즈의 비교 연산 메서드

구분상세 내용
sr.gt(other=value)sr 원소가 value 초과면 True, 아니면 False인 불리언 시리즈 반환(sr > value)
sr.ge(other=value)sr 원소가 value 이상이면 True, 아니면 False인 불리언 시리즈 반환(sr ≥ value)
sr.lt(other=value)sr 원소가 value 미만이면 True, 아니면 False인 불리언 시리즈 반환(sr < value)
sr.le(other=value)sr 원소가 value 이하면 True, 아니면 False인 불리언 시리즈 반환(sr ≤ value)
sr.eq(other=value)sr 원소가 value 같으면 True, 아니면 False인 불리언 시리즈 반환(sr == value)
sr.ne(other=value)sr 원소가 value 다르면 True, 아니면 False인 불리언 시리즈 반환(sr ≠ value)
cond = apt['거래금액'].lt(1500000) & apt['층'].ge(65)
apt.loc[cond, :]
  • between() 사용
    • isclusive 매개변수 : 양쪽 경계 포함 여부 ‘both’, ‘neither’, ‘left’, ‘right’
cond = apt['층'].ge(10) & apt['층'].le(20)
apt.loc[cond, :].shape # (88699, 15)

cond = apt['층'].between(10, 20)
apt.loc[cond, :].shape # (88699, 15)

조건에 맞는 행 선택 : 범주형(isin())

cond = apt['시군구'].eq('강남구')
apt.loc[cond, :].shape # (12790, 15)
cond = apt['시군구'].eq('강남구') | apt['시군구'].eq('서초구')
apt.loc[cond, :].shape # (23526, 15)

cond_1 = apt['시군구'].eq('강남구')
cond_2 = apt['시군구'].eq('서초구')
apt.loc[cond_1 | cond_2, :].shape # (23526, 15)
cond = apt['시군구'].eq('강남구') | apt['시군구'].eq('서초구') | apt['시군구'].eq('송파구')
apt.loc[cond, :].shape # (37970, 15)

cond_1 = apt['시군구'].eq('강남구')
cond_2 = apt['시군구'].eq('서초구')
cond_3 = apt['시군구'].eq('송파구')
apt.loc[cond_1 | cond_2 | cond_3, :].shape # (37970, 15)
  • isin : 원소가 리스트에 있으면 True, 아니면 False
cond = apt['시군구'].isin(['강남구', '서초구', '송파구'])
apt.loc[cond, :].shape # (37970, 15)
  • str.contains + 정규 표현식
cond = apt['시군구'].str.contains(pat='^강')
apt.loc[cond, :].shape # (44193, 15)

열이름으로 열 삭제

apt = apt.drop(columns=['지역코드', '매도자'])

행이름으로 행 삭제

  • drop 메서드는 눈에 보이는 행이름으로 행을 삭제
apt1.drop(index=[163874, 220404])
  • 데이터프레임에서 처음 두 행의 인덱스를 슬라이싱
apt1.index[0:2]
  • 데이터프레임의 눈에 보이는 행이름에 관계없이 처음 두 행을 삭제
apt1.drop(index=apt1.index[0:2])

열이름 변경(rename())

  • ‘기존이름’: ‘새이름’ 구조
  • columns 매개변수 생략X
apt.rename(columns={
    '아파트': '단지명',
    '건축년도': '입주년도'
})
  • columns 로 열 조회 후 열이름 일괄 변경
apt.columns

apt.columns = ['거래금액', '단지명', '시도명', '시군구', '법정동', '지번', '입주년도', '계약년도', '계약월', '계약일', '등기일자',
        '전용면적', '층']

행이름 변경(set_index(), reset_index())

  • 통계분석이나 머신러닝에서는 수치형 데이터만 x 변수로 사용
    • 문자열 → 인덱스
df2 = df1.set_index(['아파트', '시도명', '시군구', '법정동', '지번'])
  • reset_index() : 인덱스 초기화
    • drop=True : 기존 인덱스 제거
df1.reset_index(drop=True)

파생변수 생성(연속형)

apt['평당금액'] = apt['거래금액'] / apt['전용면적'] * 3.3
apt['거래금액'] = apt['거래금액'] / 10000
apt['경과년수'] = apt['계약년도'] - apt['입주년도']

구간화(binning)(np.where())

  • 연속형 변수를 특정 기준에 따라 두 개 이상의 구간으로 나누어 범주형 변수로 변환하는 것
  • 비선형, 이상치 및 결측값 문제 처리 가능
# for문으로 구간화
imsi = apt.copy()

result = []
for i in apt['경과년수']:
    if i >= 30:
        result.append('충족')
    else:
        result.append('부족')

imsi = imsi.assign(재건축 = result)
imsi.head()
  • np.where() : 조건, True일 때 반환값, False일 때 반환값 지정
    • 조건만 지정하면 True인 인덱스를 튜플로 반환
    • 조건을 만족하는 인덱스 배열을 재사용하려면 [0] 인덱싱 필요
cond = apt['경과년수'].ge(30)
apt['재건축'] = np.where(cond, '충족', '부족')
cond = apt['경과년수'].ge(30)
np.where(cond)[0]

구간화 함수(np.select(), pd.cut(), pd.qcut())

  • np.select
    • condlist : 조건의 리스트
    • choicelist : 조건에 따른 반환 값
    • default : 모든 조건을 만족하지 못할 때 반환 값
np.select(
    condlist=[
        apt['경과년수'].le(5),
        apt['경과년수'].le(10),
        apt['경과년수'].gt(10)
    ],
    choicelist=['신축', '준신축', '구축'],
    default='0'
)
  • pd.cut
    • bins : 경계를 지정(연속형 변수의 최솟값과 최댓값을 미리 확인)
apt['경과년수'].min()
apt['경과년수'].max()
pd.cut(
    x=apt['경과년수'],
    bins=[-2, 5, 10, 62],
    labels=['신축', '준신축', '구축']
)
  • pd.qcut : q 값에 따라 분할
    • quantile : 분위수(n개)
pd.qcut(
    x=apt['경과년수'],
    q=3,
    labels=['신축', '준신축', '구축']
)

열별 자료형 변환 방법

  • sr.astype(class) : 시리즈의 자료형 변환
apt['계약년도'] = apt['계약년도'].astype(str)
apt['계약월'] = apt['계약월'].astype(str)
apt['계약일'] = apt['계약일'].astype(str)
  • df.astype({col: class}) : 일부 열의 자료형 변환
apt = apt.astype(
    {
        'x1': int,
        'x2': str
    }
)
  • df[cols].astype(class) : 전체 열의 자료형 일괄 변환
cols = ['x1', 'x2', 'x3', 'x4']
apt[cols] = apt[cols].astype(int)

마치며

오늘도 많은 내용을 배웠다. 전처리 파트에 대해서 디테일하게 배우니까 저번 교육에서 배우지 못했던 정보들이 많이 있었다. 그리고 확실히 오늘 배운 내용들을 자유자제로 다룰 수 있게 되면 이후에 많은 도움이 될 것 같다.

profile
Hello I'm TaeHyunAn, Currently Studying Data Analysis

0개의 댓글