260109 [ Day 9 ] - Data (3)

TaeHyun·2026년 1월 9일

TIL

목록 보기
132/184

시작하며

오늘은 2주 차 마지막 날이다. 오늘까지 데이터 전처리를 마무리하고 다음 주 월요일에 시각화에 대해 배우면 이번 파트도 끝이 나는 것 같다.

데이터 전처리

날짜시간형 변수 생성

  • 년도, 월, 일 원소 사이에 하이픈을 추가해서 날짜시간형 타입의 문자열로 병합
    • 날짜시간 기본형 문자열 ’yyyy-mm-dd’
apt['계약일자'] = apt['계약년도'] + '-' + apt['계약월'] + '-' + apt['계약일']
  • 날짜시간형 데이터로 형변환
    • 데이터 타입 : ‘datetime64[ns]’
apt['계약일자'] = apt['계약일자'].astype('datetime64[ns]')

날짜시간형 시리즈의 dt 접근자

구분상세 내용구분상세내용
sr.dt.yearsr의 원소에서 년(year)을 정수형 시리즈로 반환sr.dt.monthsr의 원소에서 월(month)을 정수형 시리즈로 반환
sr.dt.daysr의 원소에서 일(day)을 정수형 시리즈로 반환sr.dt.hoursr의 원소에서 시(hour)을 정수형 시리즈로 반환
sr.dt.minutesr의 원소에서 분(minute)을 정수형 시리즈로 반환sr.dt.secondsr의 원소에서 초(second)을 정수형 시리즈로 반환
sr.dt.dayofweeksr의 원소에서 요일을 정수형 시리즈로 반환(월: 0, 일: 6)sr.dt.day_name()sr의 원소에서 요일을 문자열 시리즈로 반환(로케일에 따라 다름)
sr.dt.strftime(date_format)sr의 원소에서 지정한 날짜시간 포맷의 문자열 시리즈로 반환

날짜시간 주요 포맷

구분상세 내용예시구분상세 내용예시
%Y연도(4자리)‘2025’%pAM/PM 표기‘AM’
%y연도(2자리)‘25’%M분(정수, 00!59)‘02’
%m월(정수, 01~12)‘12’%S초(정수, 00~59)‘’03
%B월(Locale 전체)‘December’%f백만 분의 1초‘456789’
%b월(Locale 단축)‘Dec’%s유닉스 시간‘1703952123’
%d일(정수, 01~31)‘31’%W연중 주의 순번(00~52)‘52’
%j연중 일의 순번(001~366)‘265’%w주중 요일의 순번(0~6)‘0’
%H시(24시간, 00~23)‘22’%A요일(Locale 전체)‘Sunday’
%I시(12시간, 01~12)‘10’%a요일(Locale 단축)‘Sun’

날짜 시간형 시리즈 처리(dt)

  • dt.day_of_week
apt['계약일자'].dt.day_of_week.head()
# 0    4
# 1    0
# 2    1
# 3    5
# 4    6
# Name: 계약일자, dtype: int32
  • dt.day_name
apt['계약일자'].dt.day_name().head()
# 0      Friday
# 1      Monday
# 2     Tuesday
# 3    Saturday
# 4      Sunday
# Name: 계약일자, dtype: object
  • locale 지정
apt['계약일자'].dt.day_name(locale='ko_kr').head()
# 0    금요일
# 1    월요일
# 2    화요일
# 3    토요일
# 4    일요일
# Name: 계약일자, dtype: object
  • dt.strftime
apt['계약일자'].dt.strftime('%Y년 %m월 %d일 %a').head()
# 0    2020년 01월 31일 금
# 1    2020년 01월 06일 월
# 2    2020년 01월 14일 화
# 3    2020년 01월 04일 토
# 4    2020년 01월 12일 일
# Name: 계약일자, dtype: object

value_counts

  • 원소 개수를 간단하게 조회 가능
apt['계약일자'].dt.day_name(locale='ko_kr').value_counts()
# 계약일자
# 토요일    57664
# 월요일    33447
# 금요일    33338
# 화요일    32698
# 수요일    32663
# 목요일    31074
# 일요일    11020
# Name: count, dtype: int64
  • normalize 설정(%로 확인 가능)
apt['계약일자'].dt.day_name(locale='ko_kr').value_counts(normalize=True) * 100
# 계약일자
# 토요일    24.865462
# 월요일    14.422778
# 금요일    14.375776
# 화요일    14.099800
# 수요일    14.084707
# 목요일    13.399510
# 일요일     4.751966
# Name: proportion, dtype: float64
  • index로 정렬
dt1 = apt['계약일자'].dt.day_of_week.astype(str)
dt2 = apt['계약일자'].dt.day_name(locale='ko_kr')
(dt1 + '-' + dt2).value_counts().sort_index()
# 계약일자
# 0-월요일    33447
# 1-화요일    32698
# 2-수요일    32663
# 3-목요일    31074
# 4-금요일    33338
# 5-토요일    57664
# 6-일요일    11020
# Name: count, dtype: int64

로케일

  • 국가마다 서로 다른 문화를 가지고 있으므로 문자, 통화, 숫자 및 날짜시간 등을 표기하는 방법이 다름
  • LC_CTYPE(문자 유형)
  • LC_COLLATE(문자열 정렬)
  • LC_TIME(날짜시간 형식)
  • LC_MONETARY(통화 형식)
  • LC_MESSAGES(메시지 표시)
  • LC_NUMERIC(숫자 형식)
구분로케일 이름인코딩 방식
대한민국ko_KRUTF-8
미국en_USUTF-8
중국zh_CNUTF-8
일본ja_JPUTF-8
기본값CASCII
import locale
  • 현재 설정된 날짜시간 로케일 확인
locale.getlocale(category=locale.LC_TIME)
  • 대한민국 날짜시간 로케일로 변경
locale.setlocale(category=locale.LC_TIME, locale='ko_KR')
  • 한글로 반환 확인
apt['계약일자'].dt.strftime('%Y년 %m월 %d일 %A').head()
# 0    2020년 01월 31일 금요일
# 1    2020년 01월 06일 월요일
# 2    2020년 01월 14일 화요일
# 3    2020년 01월 04일 토요일
# 4    2020년 01월 12일 일요일
# Name: 계약일자, dtype: object

날짜시간형 간격 계산

apt['처리일수'] = apt['등기일자'] - apt['계약일자']
apt['처리일수'].tail()
# 231899   22 days
# 231900   35 days
# 231901    2 days
# 231902       NaT
# 231903       NaT
# Name: 처리일수, dtype: timedelta64[ns]
  • 일수만 확인
apt['처리일수'].dt.days.tail()
# 231899    22.0
# 231900    35.0
# 231901     2.0
# 231902     NaN
# 231903     NaN
# Name: 처리일수, dtype: float64
  • 집계함수 적용
apt['처리일수'].dt.days.mean()
# np.float64(78.1928333438109)

날짜시간 기본형이 아닌 문자열 처리

  • 날짜시간형(datetime64[ns]) 시리즈의 각 원소는 Timestamp
  • Timestamp 자료형에 timestamp 메서드를 실행하면 세계협정(UTC)값을 얻음
apt['계약일자'].iloc[0].timestamp()
# 1580428800.0
birth = pd.to_datetime(arg='1999년 8월 17일', format='%Y년 %m월 %d일')
birth
# Timestamp('1999-08-17 00:00:00')
  • Timestamp 자료형에 normalize 메서드를 실행하면 시:분:초를 초기화함
today = pd.Timestamp.today().normalize()
today
# Timestamp('2026-01-09 00:00:00')

시계열 데이터 다루기

  • freq 매개변수에 간격을 문자열로 지정('D', 'ME', 'MS')
pd.date_range(start='2026-01-01', periods=10, freq='MS')
# DatetimeIndex(['2026-01-01', '2026-02-01', '2026-03-01', '2026-04-01',
#                '2026-05-01', '2026-06-01', '2026-07-01', '2026-08-01',
#                '2026-09-01', '2026-10-01'],
#               dtype='datetime64[ns]', freq='MS')
pd.date_range(start='2026-01-01', periods=10, freq='MS')
# DatetimeIndex(['2026-01-01', '2026-02-01', '2026-03-01', '2026-04-01',
#                '2026-05-01', '2026-06-01', '2026-07-01', '2026-08-01',
#                '2026-09-01', '2026-10-01'],
#               dtype='datetime64[ns]', freq='MS')

shift , diff , pct_change , rolling

sr = pd.Series(data=values, index=dates, name='price')
sr
# 2026-01-01    87
# 2026-01-02    93
# 2026-01-03    62
# 2026-01-04    58
# 2026-01-05    59
# 2026-01-06    61
# 2026-01-07    55
# 2026-01-08    65
# 2026-01-09    50
# 2026-01-10    66
# Freq: D, Name: price, dtype: int64
  • sr.shift(peridos=1) : 이전 시점이로 1칸 이동
    • -1 입력 시 미래로 1칸
sr.shift()
# 2026-01-01     NaN
# 2026-01-02    87.0
# 2026-01-03    93.0
# 2026-01-04    62.0
# 2026-01-05    58.0
# 2026-01-06    59.0
# 2026-01-07    61.0
# 2026-01-08    55.0
# 2026-01-09    65.0
# 2026-01-10    50.0
# Freq: D, Name: price, dtype: float64
sr.shift(-1)
# 2026-01-01    93.0
# 2026-01-02    62.0
# 2026-01-03    58.0
# 2026-01-04    59.0
# 2026-01-05    61.0
# 2026-01-06    55.0
# 2026-01-07    65.0
# 2026-01-08    50.0
# 2026-01-09    66.0
# 2026-01-10     NaN
# Freq: D, Name: price, dtype: float64
  • sr.diff(periods=1) : 원소간 차이를 계산
sr.diff()
# 2026-01-01     NaN
# 2026-01-02     6.0
# 2026-01-03   -31.0
# 2026-01-04    -4.0
# 2026-01-05     1.0
# 2026-01-06     2.0
# 2026-01-07    -6.0
# 2026-01-08    10.0
# 2026-01-09   -15.0
# 2026-01-10    16.0
# Freq: D, Name: price, dtype: float64
  • sr.pct_change(periods=1) : 전일 대비 증감률 계산
sr.pct_change() * 100
# 2026-01-01          NaN
# 2026-01-02     6.896552
# 2026-01-03   -33.333333
# 2026-01-04    -6.451613
# 2026-01-05     1.724138
# 2026-01-06     3.389831
# 2026-01-07    -9.836066
# 2026-01-08    18.181818
# 2026-01-09   -23.076923
# 2026-01-10    32.000000
# Freq: D, Name: price, dtype: float64
  • sr.rolling(window=5).mean() : 5일 이동평균 계산
    • min_periods=1 속성으로 최소 기간 지정 가능(기본값=None)
2026-01-01    87.000000
2026-01-02    90.000000
2026-01-03    80.666667
2026-01-04    75.000000
2026-01-05    71.800000
2026-01-06    66.600000
2026-01-07    59.000000
2026-01-08    59.600000
2026-01-09    58.000000
2026-01-10    59.400000
Freq: D, Name: price, dtype: float64

문자열을 수치형으로 강제 변환

  • errors 매개변수에 ‘coerce’ 지정 시 수치형으로 변환할 수 없는 문자열을 결측값으로 변환
pd.to_numeric(arg=ages, errors='coerce')
# 0    24.0
# 1    31.0
# 2    29.0
# 3    43.0
# 4     NaN
# dtype: float64

문자열을 범주형으로 변환

  • 리스트를 명목형 시리즈로 변환
# ['구축', '준신축', '신축']
ctgry = pd.Series(data=gbn).astype('category')
ctgry
# 0     구축
# 1    준신축
# 2     신축
# dtype: category
# Categories (3, object): ['구축', '신축', '준신축']
  • 명목형 시리즈의 정수형 코드 확인
ctgry.cat.codes
# 0    0
# 1    2
# 2    1
# dtype: int8
  • 서열형 시리즈로 변환
    • ordered=True : 서열형
ctgry.cat.set_categories(new_categories=['구축', '준신축', '신축'], ordered=True)
# 0     구축
# 1    준신축
# 2     신축
# dtype: category
# Categories (3, object): ['구축' < '준신축' < '신축']

apply 메서드

apply

  • 데이터프레임의 행 또는 열 시리즈별로 지정한 함수를 반복 실행
    • axis 생략 시 열 시리즈 선택
    • axis 1 지정 시 행 시리즈 선택
    • func 에 실행할 함수 지정
      • 기존 함수의 경우 괄호 없이 함수명만 입력
      • 기존 함수가 없으면 사용자 정의 함수 또는 람다 표현식 대신 지정
apt['단지명'].head()
# 0     삼익대청아파트
# 1        개포자이
# 2     개포주공6단지
# 3    디에이치아너힐즈
# 4     개포주공7단지
# Name: 단지명, dtype: object
apt['단지명'].apply(func=len).head()
# 0    7
# 1    4
# 2    7
# 3    8
# 4    7
# Name: 단지명, dtype: int64
  • 시리즈 원소별로 지정한 함수를 반복 실행할 때 apply 또는 map 사용
cols = ['시도명', '시군구', '법정동', '지번']
apt[cols].shape
# (231904, 4)
apt[cols].apply(func=len)
# 시도명    231904
# 시군구    231904
# 법정동    231904
# 지번     231904
# dtype: int64
apt[cols].apply(func=len, axis=1).tail()
# 231899    4
# 231900    4
# 231901    4
# 231902    4
# 231903    4
# dtype: int64
  • 데이터프레임의 셀 값별로 지정한 함수를 반복 실행할 때 map 사용
apt[cols].map(func=len).tail()
# 시도명	시군구	법정동	지번
# 231899	5	3	3	3
# 231900	5	3	3	3
# 231901	5	3	3	3
# 231902	5	3	3	3
# 231903	5	3	3	5

iris 데이터 적용

  • statistic : 상관계수
coef = lambda x: pearsonr(x=x, y=iris['petal_width']).statistic
iris.apply(func=coef, axis=0)
# sepal_length    0.817941
# sepal_width    -0.366126
# petal_length    0.962865
# petal_width     1.000000
# dtype: float64
  • pvalue : 유희확률(귀무가설을 지지하는 정도(유의수준 0.05 기준으로 판단))
    • 귀무가설 : 두 변수의 상관관계는 0이다
corr = lambda x: pearsonr(x=x, y=iris['petal_width']).pvalue
iris.apply(func=corr, axis=0).lt(0.05)
# sepal_length    True
# sepal_width     True
# petal_length    True
# petal_width     True
# dtype: bool

문자열 시리즈 결합

join_str = lambda x: ' '.join(x)
apt[cols].apply(func=join_str, axis=1).head()
# 0      서울특별시 강남구 개포동 12
# 1    서울특별시 강남구 개포동 12-2
# 2     서울특별시 강남구 개포동 185
# 3    서울특별시 강남구 개포동 1281
# 4     서울특별시 강남구 개포동 185
# dtype: object

결측값

결측의 정의와 특징

  • 결측은 데이터에 값이 비어 있는 상태이며, 결측을 특정한 값으로 표현한 것이 결측값

결측의 종류

  • 완전 무작위 결측(MCAR) : 결측 발생이 완전히 무작위하며, 관측된 변수나 결측값 자체와 전혀 관련 없음
  • 무작위 결측(MAR) : 결측 발생이 관측된 다른 변수와 관련 있지만, 결측된 변수의 실제 값과는 무관
  • 비무작위 결측(NMAR) : 결측 발생이 관측된 변수뿐만 아니라 결측된 변수의 실제 값에도 관련 있을 때

열별 결측값 개수 확인

  • isna() : 시리즈의 원소별 결측값 여부 반환
apt.isna().sum()
# 거래금액         0
# 단지명          0
# 시도명          0
# 시군구          0
# 법정동          0
# 지번           0
# 입주년도         0
# 계약년도         0
# 계약월          0
# 계약일          0
# 등기일자    152369
# 전용면적         0
# 층            0
# 평당금액         0
# 경과년수         0
# 재건축          0
# 계약일자         0
# 처리일수    152369
# 주소           0
# dtype: int64
apt.isna().mean() * 100
# 거래금액     0.000000
# 단지명      0.000000
# 시도명      0.000000
# 시군구      0.000000
# 법정동      0.000000
# 지번       0.000000
# 입주년도     0.000000
# 계약년도     0.000000
# 계약월      0.000000
# 계약일      0.000000
# 등기일자    65.703481
# 전용면적     0.000000
# 층        0.000000
# 평당금액     0.000000
# 경과년수     0.000000
# 재건축      0.000000
# 계약일자     0.000000
# 처리일수    65.703481
# 주소       0.000000
# dtype: float64

결측값 처리 방법 : 원본 데이터로 채워넣기

  • 분석 데이터셋에 결측값이 있으면 원본 데이터로 채우는 것이 가장 좋음
  • 많은 시간과 노력이 소요될 뿐만 아니라 경우에 따라서는 불가능 할 수 있음

결측값 처리 방법 : 단순대체

  • 결측값을 대푯값(연속형이면 평균 또는 중위수, 이산형이면 최빈값 등)으로 대체하는 것
    • 추천XX
  • 일부 기준에 따라 계층별로 나눈 대푯값으로 대체할 수 있음
  • 확정적인 회귀대체법 : 회귀식의 예측값으로 대체
  • 확률적 회귀대체법 : 회귀식에 확률 오차를 추가

단순대체는 분산을 인위적으로 감소시키고 변수 간 상관관계를 왜곡할 수 있어 추론과 예측 성능에 영향을 미칠 수 있음

단순대체 fillna()

  • 결측값을 특정 값(누가봐도 결측인 것을 알수있는 값)으로 대체
apt['등기일자'].fillna('1900-01-01').head()
  • 평균값으로 대체
days_mean = apt['처리일수'].dt.days.mean()
apt['등기일자'].fillna(apt['계약일자'] + pd.Timedelta(days_mean, unit='D')).head()
# 0   2020-04-18 04:37:40.800905261
# 1   2020-03-24 04:37:40.800905261
# 2   2020-04-01 04:37:40.800905261
# 3   2020-03-22 04:37:40.800905261
# 4   2020-03-30 04:37:40.800905261
# Name: 등기일자, dtype: datetime64[ns]

결측값 처리 방법 : 결측값이 있는 행 삭제

  • 만약 특정 열에서 결측값이 일부(예를 들어 5% 미만) 있다면 결측값이 있는 행을 삭제하는 방법도 있음
    • 변수의 중요도와 결측 발생 원인에 따라 판단
  • 상대적으로 덜 중요한 열에 있는 결측값으로 인해 상대적으로 더 중요한 열에서 정상적인 값이 삭제되는 것은 좋지 않음

행 또는 열 삭제 dropna()

  • 결측값이 있는 행을 모두 삭제
  • axis=1 지정 시 결측이 있는 열을 모두 삭제

결측값을 이전 셀 값으로 채우기 ffill() , interpolate()

  • ffill() : 결측값을 이전 셀 값으로 채움
na = pd.read_excel('NA_Sample.xlsx')
na
# 구분	항목	값
# 0	A	a	1.2
# 1	NaN	b	NaN
# 2	NaN	c	3.5
# 3	NaN	d	4.2
# 4	B	a	1.7
# 5	NaN	b	2.6
# 6	NaN	c	NaN
# 7	NaN	d	3.6
  • interpolate() : 결측값을 선형 보간법으로 채움
na['구분'] = na['구분'].ffill()
na
# 구분	항목	값
# 0	A	a	1.2
# 1	A	b	NaN
# 2	A	c	3.5
# 3	A	d	4.2
# 4	B	a	1.7
# 5	B	b	2.6
# 6	B	c	NaN
# 7	B	d	3.6
  • 범주별 평균으로 대체
    • transform() : 원본과 같은 길이 반환
na['값'].fillna(na.groupby(by='구분')['값'].transform(func='mean'))
# 0    1.200000
# 1    2.966667
# 2    3.500000
# 3    4.200000
# 4    1.700000
# 5    2.600000
# 6    2.633333
# 7    3.600000
# Name: 값, dtype: float64

결측값 처리 방법 : 다중대체

  • 결측값을 확률적 회귀대체로 채운 여러 개의 데이터셋을 생성하고, 각 데이터셋으로 동일한 분석을 반복 수행한 결과를 통합

데이터프레임 결합

  • 두 데이터프레임의 열 이름이 순서까지 정확하게 같은지 확인
df1.columns.equals(df2.columns)
# True

행 방향 결합

  • ignore_index=True : 인덱스 번호 초기화
pd.concat([df1, df2], ignore_index=True)
# 거래금액	단지명	시도명	시군구	법정동	지번
# 0	10.05	삼익대청아파트	서울특별시	강남구	개포동	12
# 1	20.30	개포자이	서울특별시	강남구	개포동	12-2
# 2	18.00	개포주공6단지	서울특별시	강남구	개포동	185
# 3	28.50	디에이치아너힐즈	서울특별시	강남구	개포동	1281
# 4	17.10	개포주공7단지	서울특별시	강남구	개포동	185
# 5	7.40	한신아파트상가동유치원동(102~102)	서울특별시	중랑구	중화동	450
# 6	5.05	삼익아파트.상가동	서울특별시	중랑구	중화동	438
# 7	6.00	한신아파트상가동유치원동(103~109)	서울특별시	중랑구	중화동	450
# 8	6.70	한신아파트상가동유치원동(102~102)	서울특별시	중랑구	중화동	450
# 9	1.10	범양프레체	서울특별시	중랑구	중화동	208-4
  • 열 이름이 다르면 셀 값을 결측값으로 채움
pd.concat([df1, df2], ignore_index=True)
# 거래금액	단지명	시도명	시군구	법정동	지번	아파트단지명
# 0	10.05	삼익대청아파트	서울특별시	강남구	개포동	12	NaN
# 1	20.30	개포자이	서울특별시	강남구	개포동	12-2	NaN
# 2	18.00	개포주공6단지	서울특별시	강남구	개포동	185	NaN
# 3	28.50	디에이치아너힐즈	서울특별시	강남구	개포동	1281	NaN
# 4	17.10	개포주공7단지	서울특별시	강남구	개포동	185	NaN
# 5	7.40	NaN	서울특별시	중랑구	중화동	450	한신아파트상가동유치원동(102~102)
# 6	5.05	NaN	서울특별시	중랑구	중화동	438	삼익아파트.상가동
# 7	6.00	NaN	서울특별시	중랑구	중화동	450	한신아파트상가동유치원동(103~109)
# 8	6.70	NaN	서울특별시	중랑구	중화동	450	한신아파트상가동유치원동(102~102)
# 9	1.10	NaN	서울특별시	중랑구	중화동	208-4	범양프레체

열 방향 결합

  • 행이름이 다르면 결측값으로 채움
  • reset_index(drop=True) : 인덱스 초기화로 행이름 통일
  • ignore_index=True 지정하면 컬럼명 초기화
pd.concat([df1, df2], axis=1)
# 거래금액	단지명	시도명	시군구	법정동	지번	거래금액	아파트단지명	시도명	시군구	법정동	지번
# 0	10.05	삼익대청아파트	서울특별시	강남구	개포동	12	NaN	NaN	NaN	NaN	NaN	NaN
# 1	20.30	개포자이	서울특별시	강남구	개포동	12-2	NaN	NaN	NaN	NaN	NaN	NaN
# 2	18.00	개포주공6단지	서울특별시	강남구	개포동	185	NaN	NaN	NaN	NaN	NaN	NaN
# 3	28.50	디에이치아너힐즈	서울특별시	강남구	개포동	1281	NaN	NaN	NaN	NaN	NaN	NaN
# 4	17.10	개포주공7단지	서울특별시	강남구	개포동	185	NaN	NaN	NaN	NaN	NaN	NaN
# 220885	NaN	NaN	NaN	NaN	NaN	NaN	7.40	한신아파트상가동유치원동(102~102)	서울특별시	중랑구	중화동	450
# 220886	NaN	NaN	NaN	NaN	NaN	NaN	5.05	삼익아파트.상가동	서울특별시	중랑구	중화동	438
# 220887	NaN	NaN	NaN	NaN	NaN	NaN	6.00	한신아파트상가동유치원동(103~109)	서울특별시	중랑구	중화동	450
# 220888	NaN	NaN	NaN	NaN	NaN	NaN	6.70	한신아파트상가동유치원동(102~102)	서울특별시	중랑구	중화동	450
# 220889	NaN	NaN	NaN	NaN	NaN	NaN	1.10	범양프레체	서울특별시	중랑구	중화동	208-4
pd.concat([df1, df2], axis=1)
# 거래금액	단지명	시도명	시군구	법정동	지번	거래금액	아파트단지명	시도명	시군구	법정동	지번
# 0	10.05	삼익대청아파트	서울특별시	강남구	개포동	12	7.40	한신아파트상가동유치원동(102~102)	서울특별시	중랑구	중화동	450
# 1	20.30	개포자이	서울특별시	강남구	개포동	12-2	5.05	삼익아파트.상가동	서울특별시	중랑구	중화동	438
# 2	18.00	개포주공6단지	서울특별시	강남구	개포동	185	6.00	한신아파트상가동유치원동(103~109)	서울특별시	중랑구	중화동	450
# 3	28.50	디에이치아너힐즈	서울특별시	강남구	개포동	1281	6.70	한신아파트상가동유치원동(102~102)	서울특별시	중랑구	중화동	450
# 4	17.10	개포주공7단지	서울특별시	강남구	개포동	185	1.10	범양프레체	서울특별시	중랑구	중화동	208-4

데이터프레임 병합

두 외래키에서 서로 일치하는 원소가 있는가?
오른쪽 외래키에 중복된 원소가 있는가?

  • 두 데이터프레임의 외래키가 M:N 관계라면 둘 중 하나(오른쪽)에서 중복을 제거하거나 전처리 실행

중복 행 삭제 duplicated() , drop_duplicates()

  • duplicated() : 중복 여부를 True False로 반환
    • keep='first' : 중복 건에 대해 처음 나오는 원소는 False 나머지는 True
    • keep='last' : 중복 건에 대해 마지막에 나오는 원소는 False 나머지는 True
    • keep=False를 지정하면 모든 중복 건에 대해 True 지정
    • subset 매개변수에 열이름 리스트를 지정하면 해당 컬럼을 기준으로 중복 여부 확인
apt['주소'].duplicated(keep=False).sum()
apt.duplicated(subset='주소', keep=False).sum()
# np.int64(220141)
  • drop_duplicates() : 중복인 값 제거
    • keep='first' : 중복이 아닌 값은 남기고, 중복이면 처음 나온 값만 남김
    • keep='last' : 중복이 아닌 값은 남기고, 중복이면 마지막에 나온 값만 남김
    • keep=False : 중복이 아닌 값은 남기고, 중복이면 모두 제거
apt.drop_duplicates(subset='주소', keep='first')

두 데이터프레임의 외래키에 중복 건이 있으면 병합 결과 행 개수가 늘어남

apt.shape # (231904, 17)
pd.merge(left=apt, right=dtl, how='left', on='주소').shape # (235991, 28)
  • 중복인 행 모두 삭제
dups = dtl['주소'].duplicated(keep=False)
dtl.loc[dups, :]
  • 중복 첫 행은 남기고 삭제
dtl = dtl.drop_duplicates(subset='주소')
  • 데이터프레임 병합
    • how : 병합 방법 (inner, outer, left)
    • on : 외래키의 열이름 지정, 두 개 이상일 때는 리스트로 지정
      • 두 데이터프레임에서 일치하는 열이름이 모두 열이름일 때는 생략 가능
      • 2개 이상인 경우 대응하는 순서를 맞춰 리스트로 지정
cols = ['주소', '세대수', '주차대수']
apt = pd.merge(left=apt, right=dtl[cols], how='inner', on='주소')

외래키 아닌 열이름이 같은 경우

  • 병합 시 자동으로 열이름에 아래 첨자를 추가
    • 이후 열이름 변경
  • 열이름을 미리 변경 후 병합

데이터프레임 형태(Wide Type, Long Type)

  • 분석 과정에서는 Wide Type을 많이 사용
  • 그래프를 그리기 위해 Long Type으로 변환하기도 함

Long Type으로 변환 melt()

  • id_vars : 기준 변수명 리스트 지정
  • value_vars : 행 방향으로 변환할 변수명 리스트 지정
  • ignore_index=False : 인덱스 초기화 False
    • True 시 인덱스가 새로 정렬되어 원하는 결과 X
    • 오름차순 정렬한 결과 확인
elong = widen.melt(
    id_vars=id_vars,
    value_vars=value_vars,
    ignore_index=False
)
elong.sort_index()
# 단지명	variable	value
# 0	목동휘버스	세대수	24
# 0	목동휘버스	주차대수	28
# 1	북가좌삼호	세대수	616
# 1	북가좌삼호	주차대수	749
# 2	하이츠	세대수	24
# 2	하이츠	주차대수	50

Wide Type으로 변환 pivot()

  • index : 기준 변수명 리스트 지정
  • columns : 열 방향으로 펼칠 변수명 지정
  • values : 값으로 채울 변수명 지정
widen = elong.pivot(
    index=id_vars,
    columns='variable',
    values='value'
)
widen
# variable	세대수	주차대수
# 단지명		
# 목동휘버스	24	28
# 북가좌삼호	616	749
# 하이츠	24	50
  • 자동으로 인덱스, 컬럼 이름 설정
widen.index
# Index(['목동휘버스', '북가좌삼호', '하이츠'], dtype='object', name='단지명')
widen.columns
# Index(['세대수', '주차대수'], dtype='object', name='variable')
  • reset_index 로 index.name 초기화
    • 데이터프레임의 행이름을 초기화할 때 index.name이 없으면 기존 인덱스를 첫 번째 열로 추가하면서 열이름은 'index'가 됨
widen = widen.reset_index()
# variable	단지명	세대수	주차대수
# 0	목동휘버스	24	28
# 1	북가좌삼호	616	749
# 2	하이츠	24	50
  • columns.name이 불편하면 빈 문자열로 바꾸거나 None으로 삭제 가능
widen.columns.name # 'variable'

widen.columns.name = ''
widen.columns # Index(['단지명', '세대수', '주차대수'], dtype='object', name='')

widen.columns.name = None
widen.columns # Index(['단지명', '세대수', '주차대수'], dtype='object')

마치며

매일 느끼는 거지만 진도가 상당히 빠른 것 같다. 하지만 전부 중요한 내용들이고 많은 도움이 되는 내용들이라 하나도 놓칠 수 없을 것 같다. 주말간 복습도 꾸준히 하면서 더 익숙해져야겠다.

profile
Hello I'm TaeHyunAn, Currently Studying Data Analysis

0개의 댓글