본 포스팅에 들어가기에 앞서 데이터 전처리에 대한 전체적인 내용이 궁금하신 분들은 아래의 링크를 확인하고 오시면 좋을 것 같습니다.
이번 주제는 결합에 대한 내용입니다.
결합
실제 현업에서 데이터는 종류별로 테이블이 나뉘기 때문에 하나의 테이블에 필요한 데이터가 모두 포함된 경우는 드뭅니다.
데이터 분석용인 데이터는 하나의 테이블에 모두 정리된 가로로 긴 데이터가 이상적입니다.
마스터 테이블에서 정보 얻기
레코드 테이블과 마스터 테이블의 결합은 가장 자주 사용되는 결합입니다.
마스터 테이블은 특정 요소에 대한 공통의 데이터를 모아둔 테이블을 말합니다.
Ex. 고객 마스터 테이블에는 고객별 이름이나 성별, 주소 등 개인 정보가 있습니다.
마스터 테이블에는 유일한 ID가 존재하는데, 이를 이용하 레코드 데이터와의 결합이 이루어집니다.
이때, 결합할 테이블의 크기를 가능한 작게 하여 메모리를 적게 사용해야 합니다.
불필요한 데이터까지 결합 처리의 대상이 되는 것은 좋은 방법이 아닙니다.
예시 상황을 들어보겠습니다.
예약 테이블과 호텔테이블이 다음과 같이 존재할 때, 숙박 인원수가 한 명인 비즈니스 호텔의 예약 레코드를 추출
테이블을 도출하는 방법이 크게 두 가지가 있을 것입니다.
- hotel_id를 기준으로 일단 합치고 필터링
- 각 데이터를 필터링 후 hotel_id 기준으로 결합
두 방법 모두 같은 결과가 도출되지만 계산 비용으로 봤을 때 후자의 방법이 시간이 덜 걸릴 것입니다.
따라서 먼저 숙박 인원수가 한 명인 예약 레코드를 나타내고 비즈니스인 호텔의 데이터와 합치는 방법을 사용하겠습니다.
pd.merge(df.query('people_num == 1'),
df_hotel.query("is_business"),
on = "hotel_id", how = "inner")
조건에 따라 결합할 마스터 테이블 변경하기
값에 따라 결합 대상을 변경하는 결합 처리 같은 특별한 전처리가 요구되기도 합니다.
이번에는 조금 특별한 상황을 예로 들어보겠습니다.
호텔 예약 사이트에서 호텔별로 다른 호텔을 추천하고 싶은 상황입니다.
이때 같은 지역의 호텔만을 추천하려고 합니다.
하지만 지역에 따라 호텔의 수가 충분하지 않아 소규모 지역 단위에 추천 후보 수가 20건 미만이면 같은 대규모 지역 단위의 호텔을, 20건 이상이면 같은 소규모 지역 단위의 호텔을 후보로 하는 테이블을 만드는 코드를 작성해 봅시다.
먼저 지역별 호텔의 수를 계산해 줍니다.
small_area_mst = df_hotel.groupby(["big_area_name", "small_area_name"]).size().reset_index()
small_area_mst.columns = ["big_area_name", "small_area_name", "hotel_cnt"]
small_area_mst.head()
호텔의 수가 20 이상이면 small_area_name을, 20 미만이면 big_are_name을 지정합니다.
※ 본인의 호텔은 제외돼야 하므로 -1을 해줍니다.
small_area_mst["join_area_id"] = np.where(small_area_mst["hotel_cnt"] - 1 >= 20,
small_area_mst["small_area_name"],
small_area_mst["big_area_name"])
small_area_mst.drop(["hotel_cnt", "big_area_name"], axis = 1, inplace = True)
small_area_name을 키로 hotel_id별로 join_area_id를 결합합니다.
base_hotel_mst = pd.merge(df_hotel, small_area_mst, on = "small_area_name").loc[:, ["hotel_id", "join_area_id"]]
base_hotel_mst.head()
head 함수를 통해 살짝 확인해 보면 위에 나와있는 호텔들은 모두 지역 내 호텔의 수가 20개 미만이기 때문에 대규모 지역 단위(D)의 호텔을 추천받는 것을 알 수 있습니다.
이제 구체적인 호텔 명을 받아야 됩니다.
이때 join_area_id에는 대규모 지역과 소규모 지역이 모두 있으므로 추천받는 호텔들도 그에 맞게 준비해야 합니다.
따라서 아래와 같은 그림처럼 데이터를 만들어서 기존 base_hotel_mst와 결합시켜야 합니다.
※ 대신 같은 호텔이 결합되면 안 되므로 조건을 붙여줘야 합니다.
import gc
gc.collect()
recommend_hotel_mst = pd.concat([df_hotel[["small_area_name", "hotel_id"]].rename(columns = {"small_area_name": "join_area_id"}, inplace = False),
df_hotel[["big_area_name", "hotel_id"]].rename(columns = {"big_area_name": "join_area_id"}, inplace = False)])
recommend_hotel_mst.rename(columns = {"hotel_id" : "rec_hotel_id"},inplace = True)
pd.merge(base_hotel_mst, recommend_hotel_mst, on = "join_area_id").loc[:, ["hotel_id", "rec_hotel_id"]].query("hotel_id != rec_hotel_id")
과거 데이터에서 정보 얻기
데이터 분석에서 시간 데이터 열을 다루는 분석은 자주 발생합니다.
하지만 시간 데이터는 쉽게 버그(의도치 않은 변환 처리 등)나 누수(예측 모델에 사용하는 데이터에 미래 데이터가 섞이는 등)를 유발하므로 다루는 데 주의가 필요합니다.
n건 전의 데이터 얻기
예약 테이블의 모든 행에 고객이 이전에 예약했던 두 번의 예약 금액 정보를 첨부
result = df.groupby('customer_id') .apply(lambda group: group.sort_values(by='reserve_datetime', axis=0, inplace=False))
result['before_price'] = result['total_price'].shift(periods=2)
result.head()
과거 n건의 합계값
예약 테이블의 모든 행에 자신의 행에서 같은 고객의 이전 예약 세 건의 예약 금액 정보를 추출
과거 예약 건수가 세 건 미만이면 없음으로 표시
result = df.groupby('customer_id').apply(lambda x: x.sort_values(by = "reserve_datetime", ascending = True)).reset_index(drop = True)
result["price_sum"] = pd.Series(result.loc[:, ["customer_id", "total_price"]].groupby("customer_id").rolling(center = False, window = 3, min_periods = 3).sum().reset_index(drop = True).loc[:, "total_price"])
과거 n건의 평균값
예약 테이블의 모든 행에 자신의 행을 포함하지 않고 한 건 이전의 데이터에서 세건 이전까지의 평균 예약 금액을 첨부하고, 과거 예약 건수가 세 건 미만이면 존재하는 건수의 평균값을 계산
result = df.groupby("customer_id").apply(lambda x: x.sort_values(by = "reserve_datetime", ascending = True)).reset_index(drop = True)
result["price_avg"] = pd.Series(result.groupby("customer_id")["total_price"].rolling(center= False, window = 3, min_periods = 1).mean().reset_index(drop = True))
# 테이블의 모든 행에 자신의 행을 포함하지 않고 한 건 이전의 데이터에서 세 건 이전까지의 평균 예약 금액을 첨부
# 과거 예약 건수가 세건 미만이면 존재하는 건수의 평균 계산
# 예약이 한 건도 없으면 값을 가지지 않음
result.head(10)
과거 n일의 합계값
예약 테이블의 모든 데이터에 자신을 포함하지 않으면서 같은 고객의 지난 90일간의 합계 예약 금액 정보를 첨부
예약이 없으면 값을 0으로 표시
import pandas.tseries.offsets as offsets
import operator
df["reserve_datetime"] = pd.to_datetime(df["reserve_datetime"], format = "%Y-%m-%d %H:%M:%S")
sum_table = pd.merge(df[["reserve_id", "customer_id", "reserve_datetime"]],
df[["customer_id", "reserve_datetime", "total_price"]].rename(columns = {"reserve_datetime": "reserve_datetime_before"}),
on = "customer_id")
sum_table = sum_table[operator.and_(sum_table["reserve_datetime"] > sum_table["reserve_datetime_before"],
sum_table["reserve_datetime"] + offsets.Day(-90) <= sum_table["reserve_datetime_before"])].groupby("reserve_id")["total_price"].sum().reset_index()
sum_table.columns = ["reserve_id", "total_price_sum"]
pd.merge(df, sum_table, on = "reserve_id", how = "left").fillna(0)
지금까지 데이터 구조 전처리 중 결합에 대해 알아보았습니다.
대부분의 데이터는 목적별로 분류되어 있는 경우가 다수이기 때문에 결합에 대해 잘 이해하고 있으면 나중에 데이터 분석하실 때 쉽게 전처리할 수 있을 것입니다.
포스팅 내용 중 다른 생각이 있는 분 혹은 수정해야 할 부분이 있으시면 댓글을 통해 그 의견을 나눠보면 너무 좋을 것 같습니다.
※ 본 포스팅의 내용은 데이터 전처리 대전을 참고하였습니다.
'Data preprocessing' 카테고리의 다른 글
[데이터 전처리#6] 데이터 구조 전처리 - 전개 (0) | 2023.07.10 |
---|---|
[데이터 전처리#5] 데이터 구조 전처리 - 생성 (1) | 2023.07.10 |
[데이터 전처리#3] 데이터 구조 전처리 - 집약 (0) | 2023.07.04 |
[데이터 전처리#2] 데이터 구조 전처리 - 추출 (0) | 2023.07.02 |
[데이터 전처리#1] 데이터 전처리 개요 (0) | 2023.06.29 |