{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "4e002a6a-b573-41bf-bbd8-e9ee8b1456fd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "--- 데이터 상위 5개 행 확인 ---\n",
      "                     제목                                                소재지  \\\n",
      "0        문래동금호어울림 103 동   서울특별시 영등포구 문래동3가 97 지도보기(도로명:서울특별시 영등포구 문래로 137)   \n",
      "1               강변삼성래미안  서울특별시 영등포구 당산동 376 지도보기(도로명:서울특별시 영등포구 버드나루로 130)   \n",
      "2           한신18차 337 동    서울특별시 서초구 잠원동 49-17 지도보기(도로명:서울특별시 서초구 잠원로 195)   \n",
      "3  우장산IPARK,e편한세상 135 동                                                NaN   \n",
      "4             장미1차 14 동    서울특별시 송파구 신천동 7 지도보기(도로명:서울특별시 송파구 올림픽로35길 104)   \n",
      "\n",
      "     지역1   지역2    지역3             지역4      매매희망가격        건설회사     면적1     면적2  \\\n",
      "0  서울특별시  영등포구  문래동3가        문래동금호어울림   550000000     금호건설(주)  112.53   84.97   \n",
      "1  서울특별시  영등포구    당산동         강변삼성래미안   520000000     삼성물산(주)  108.54   78.90   \n",
      "2  서울특별시   서초구    잠원동           한신18차   550000000     한신공영(주)   56.20   50.64   \n",
      "3  서울특별시   강서구    화곡동  우장산IPARK,e편한세상   700000000         NaN  137.41  114.98   \n",
      "4  서울특별시   송파구    신천동            장미1차  1350000000  라이프주택개발(주)  185.12  169.49   \n",
      "\n",
      "   ...  현관구조       취득세      중개보수   해당층/총층      난방연료/방식             단지규모  \\\n",
      "0  ...   계단식   6050000   2200000     중층/-  도시가스 / 개별난방     총 3 개동 134가구   \n",
      "1  ...   계단식   5720000   2080000  중층/총18층  도시가스 / 개별난방    총 13 개동 801가구   \n",
      "2  ...   복도식   6050000   2200000  9층/총13층   열병합 / 지역난방     총 3 개동 308가구   \n",
      "3  ...   NaN  16800000   6300000      NaN          NaN              NaN   \n",
      "4  ...   계단식  47250000  12150000  3층/총14층   열병합 / 지역난방  총 21 개동 2,100가구   \n",
      "\n",
      "     대지지분         입주일              주차대수  \\\n",
      "0     NaN  2006.10.20    총154대(가구당1.1대)   \n",
      "1     NaN  2002.06.28  총1,013대(가구당1.3대)   \n",
      "2      8㎡     1983.07    총308대(가구당1.0대)   \n",
      "3     NaN         NaN               NaN   \n",
      "4  95.18㎡     1979.01  총2,100대(가구당1.0대)   \n",
      "\n",
      "                                             매물특징  \n",
      "0  입주가능일: 협의 남향34평형, 집상태도 아주좋아요문래역도보34분거리 굿추천드립니다  \n",
      "1                                    수리  입주가능합니다.  \n",
      "2                                          임대수요풍부  \n",
      "3                                             NaN  \n",
      "4           입주가능일: 협의가능 남향,재건축2채분양 대상인 56평형 ,추천급매  \n",
      "\n",
      "[5 rows x 24 columns]\n",
      "\n",
      "--- 데이터 요약 정보 확인 ---\n",
      "<class 'pandas.DataFrame'>\n",
      "RangeIndex: 23681 entries, 0 to 23680\n",
      "Data columns (total 24 columns):\n",
      " #   Column   Non-Null Count  Dtype  \n",
      "---  ------   --------------  -----  \n",
      " 0   제목       23681 non-null  str    \n",
      " 1   소재지      23658 non-null  str    \n",
      " 2   지역1      23681 non-null  str    \n",
      " 3   지역2      23681 non-null  str    \n",
      " 4   지역3      23681 non-null  str    \n",
      " 5   지역4      23681 non-null  str    \n",
      " 6   매매희망가격   23681 non-null  int64  \n",
      " 7   건설회사     22781 non-null  str    \n",
      " 8   면적1      23681 non-null  float64\n",
      " 9   면적2      23681 non-null  float64\n",
      " 10  방수       23115 non-null  float64\n",
      " 11  욕실수      23115 non-null  float64\n",
      " 12  건폐율      19714 non-null  object \n",
      " 13  방향       23658 non-null  str    \n",
      " 14  현관구조     23403 non-null  str    \n",
      " 15  취득세      23681 non-null  int64  \n",
      " 16  중개보수     23681 non-null  int64  \n",
      " 17  해당층/총층   23658 non-null  str    \n",
      " 18  난방연료/방식  23658 non-null  str    \n",
      " 19  단지규모     23658 non-null  str    \n",
      " 20  대지지분     1684 non-null   str    \n",
      " 21  입주일      23647 non-null  object \n",
      " 22  주차대수     23647 non-null  str    \n",
      " 23  매물특징     22269 non-null  str    \n",
      "dtypes: float64(4), int64(3), object(2), str(15)\n",
      "memory usage: 4.3+ MB\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# 파일 경로를 지정합니다.\n",
    "file_path = \"data/r114_서울시_아파트_매매정보_20151002.xlsx\"\n",
    "df = pd.read_excel(file_path)\n",
    "\n",
    "# 데이터 분석을 시작하기 전, 상위 5개의 행을 출력하여 데이터를 확인합니다.\n",
    "print(\"\\n--- 데이터 상위 5개 행 확인 ---\")\n",
    "print(df.head())\n",
    "\n",
    "# 데이터의 행과 열 개수, 결측치 여부, 데이터 형태(type) 등 전반적인 정보를 확인합니다.\n",
    "print(\"\\n--- 데이터 요약 정보 확인 ---\")\n",
    "print(df.info())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "46b00173-0c90-4a0f-8469-75a84b43843a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "제목             0\n",
      "소재지           23\n",
      "지역1            0\n",
      "지역2            0\n",
      "지역3            0\n",
      "지역4            0\n",
      "매매희망가격         0\n",
      "건설회사         900\n",
      "면적1            0\n",
      "면적2            0\n",
      "방수           566\n",
      "욕실수          566\n",
      "건폐율         3967\n",
      "방향            23\n",
      "현관구조         278\n",
      "취득세            0\n",
      "중개보수           0\n",
      "해당층/총층        23\n",
      "난방연료/방식       23\n",
      "단지규모          23\n",
      "대지지분       21997\n",
      "입주일           34\n",
      "주차대수          34\n",
      "매물특징        1412\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "# 각 열의 결측치 개수를 화면에 출력합니다.\n",
    "print(df.isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "6e2bf05e-0722-42fa-8698-44dc5b3da448",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "소재지 열의 최종 결측치 개수: 0\n",
      "\n",
      "--- 정제 및 결측치 처리 후 소재지 확인 ---\n",
      "0     서울특별시 영등포구 문래동3가 97\n",
      "1      서울특별시 영등포구 당산동 376\n",
      "2     서울특별시 서초구 잠원동 49-17\n",
      "3           서울특별시 강서구 화곡동\n",
      "4         서울특별시 송파구 신천동 7\n",
      "5        서울특별시 용산구 신계동 48\n",
      "6      서울특별시 동대문구 이문동 225\n",
      "7      서울특별시 서대문구 대현동 144\n",
      "8    서울특별시 관악구 신림동 10-472\n",
      "9     서울특별시 광진구 구의동 631-1\n",
      "Name: 소재지, dtype: object\n"
     ]
    }
   ],
   "source": [
    "# 1. 기존 '소재지' 데이터에서 불필요한 '지도보기...' 이후 텍스트를 제거하여 정제합니다.\n",
    "# ' 지도보기'라는 글자를 기준으로 문자열을 나누고, 그 앞부분만 가져옵니다.\n",
    "df['소재지'] = df['소재지'].astype(str).str.split(' 지도보기').str[0]\n",
    "\n",
    "# 2. '소재지' 열의 결측치(또는 문자열 'nan', 빈칸)를 판별하기 위한 조건문을 만듭니다.\n",
    "is_null_address = df['소재지'].isnull() | (df['소재지'] == 'nan') | (df['소재지'].str.strip() == '')\n",
    "\n",
    "# 3. 결측치가 있는 행은 이미 완벽하게 채워져 있는 '지역1', '지역2', '지역3' 열을 합쳐서 채워줍니다.\n",
    "# 예: \"서울특별시\" + \" \" + \"송파구\" + \" \" + \"신천동\" -> \"서울특별시 송파구 신천동\"\n",
    "df.loc[is_null_address, '소재지'] = df['지역1'] + ' ' + df['지역2'] + ' ' + df['지역3']\n",
    "\n",
    "# 4. 결과 확인을 위해 상위 10개 행을 출력합니다.\n",
    "print(\"소재지 열의 최종 결측치 개수:\", df['소재지'].isnull().sum())\n",
    "print(\"\\n--- 정제 및 결측치 처리 후 소재지 확인 ---\")\n",
    "print(df['소재지'].head(10))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "2a3df7db-c191-4c33-b57c-33af37fc465f",
   "metadata": {},
   "outputs": [],
   "source": [
    "df['방향'] = df['방향'].fillna('-')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "8cdc4322-0ffe-4dc8-b6d1-779c84cf36a7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "건설회사 열의 최종 결측치 개수: 0\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "# 최빈값을 구하는 람다 함수 정의 (그룹 내 데이터가 없을 경우 NaN 반환)\n",
    "get_mode = lambda x: x.mode().iloc[0] if not x.mode().empty else None\n",
    "\n",
    "# 1. '건설회사' 열에서 '(주)' 문자열을 삭제합니다.\n",
    "df['건설회사'] = df['건설회사'].str.split('/').str[0]\n",
    "df['건설회사'] = df['건설회사'].str.replace('(주)', '', regex=False).str.strip()\n",
    "df['건설회사'] = df['건설회사'].str.replace('건설부문', '', regex=False).str.strip()\n",
    "\n",
    "# 1단계: '동일 지역(동)'이고 '동일 평수(면적2)'인 매물들의 건설회사 최빈값으로 채우기\n",
    "df['건설회사'] = df['건설회사'].fillna(df.groupby(['지역3', '면적2'])['건설회사'].transform(get_mode))\n",
    "\n",
    "# 2단계: 면적이 소수점 단위로 미세하게 달라 매칭되지 않는 경우를 위해 \n",
    "# '동일 지역(동)' 내에서 면적을 10m² 단위로 묶어(유사 평형대) 해당 그룹의 최빈값으로 채우기\n",
    "df['면적_구간'] = (df['면적2'] // 10) * 10\n",
    "df['건설회사'] = df['건설회사'].fillna(df.groupby(['지역3', '면적_구간'])['건설회사'].transform(get_mode))\n",
    "\n",
    "# 3단계: 그래도 남은 결측치는 해당 구('지역2') 또는 동('지역3')의 전체 최빈값으로 최종 보완\n",
    "df['건설회사'] = df['건설회사'].fillna(df.groupby('지역3')['건설회사'].transform(get_mode))\n",
    "df['건설회사'] = df['건설회사'].fillna(df.groupby('지역2')['건설회사'].transform(get_mode))\n",
    "\n",
    "# 4단계: 마지막 예외 처리 (데이터가 아예 없는 극소수의 경우 전체 최빈값 적용)\n",
    "if not df['건설회사'].mode().empty:\n",
    "    global_company_mode = df['건설회사'].mode().iloc[0]\n",
    "    df['건설회사'] = df['건설회사'].fillna(global_company_mode)\n",
    "\n",
    "# 사용 완료한 임시 열 삭제\n",
    "df = df.drop(columns=['면적_구간'])\n",
    "\n",
    "# 결측치 처리 결과 확인\n",
    "print(\"건설회사 열의 최종 결측치 개수:\", df['건설회사'].isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "962744cf-e1ba-4f8b-a70d-0473221360d4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "방수 열의 결측치 개수: 0\n",
      "욕실수 열의 결측치 개수: 0\n"
     ]
    }
   ],
   "source": [
    "# 1. '방수' 또는 '욕실수'가 비어 있는 행의 인덱스를 구합니다.\n",
    "missing_indices = df[df['방수'].isnull() | df['욕실수'].isnull()].index\n",
    "\n",
    "# 2. 결측치(NaN)가 없이 두 정보가 모두 잘 입력된 정상 데이터를 분리합니다.\n",
    "valid_df = df[df['방수'].notnull() & df['욕실수'].notnull()]\n",
    "\n",
    "# 3. 결측치가 있는 행들을 하나씩 확인하며 값을 채웁니다.\n",
    "for idx in missing_indices:\n",
    "    # 현재 확인 중인 매물의 '면적2' 값을 가져옵니다.\n",
    "    target_area = df.loc[idx, '면적2']\n",
    "    \n",
    "    # 면적 차이를 계산합니다.\n",
    "    area_difference = (valid_df['면적2'] - target_area).abs()\n",
    "    \n",
    "    # 면적 차이가 3 이하인 매물들만 필터링합니다. (+-3m2 이내)\n",
    "    range_listings = valid_df[area_difference <= 3]\n",
    "    \n",
    "    # 만약 +-3m2 이내에 해당하는 매물이 하나도 없다면, 가장 가까운 면적의 매물을 선택합니다.\n",
    "    if range_listings.empty:\n",
    "        min_difference = area_difference.min()\n",
    "        range_listings = valid_df[area_difference == min_difference]\n",
    "    \n",
    "    # '방수'가 비어 있다면, 선택된 매물들의 방수 최빈값으로 채웁니다.\n",
    "    if pd.isnull(df.loc[idx, '방수']):\n",
    "        room_mode = range_listings['방수'].mode()\n",
    "        if not room_mode.empty:\n",
    "            df.loc[idx, '방수'] = room_mode.iloc[0]\n",
    "            \n",
    "    # '욕실수'가 비어 있다면, 선택된 매물들의 욕실수 최빈값으로 채웁니다.\n",
    "    if pd.isnull(df.loc[idx, '욕실수']):\n",
    "        bath_mode = range_listings['욕실수'].mode()\n",
    "        if not bath_mode.empty:\n",
    "            df.loc[idx, '욕실수'] = bath_mode.iloc[0]\n",
    "\n",
    "# 4. 결측치가 모두 정상적으로 처리되었는지 개수를 다시 확인합니다.\n",
    "print(\"방수 열의 결측치 개수:\", df['방수'].isnull().sum())\n",
    "print(\"욕실수 열의 결측치 개수:\", df['욕실수'].isnull().sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "753cb9f3-8eb5-48d4-aa83-e1b698f6f495",
   "metadata": {},
   "source": [
    "### 건폐율 결측치 채우기\n",
    "건폐율은 임의의 평균값으로 채우면 데이터의 왜곡이 심해질 수 있습니다. 주상복합인지 일반 아파트인지, 혹은 상업지역인지 주거지역인지에 따라 수치 차이가 크기 때문입니다. 따라서 다음과 같은 단계적 매칭 방법을 추천합니다.\n",
    "\n",
    "* **1단계: 동일 단지(아파트명)의 데이터 활용 (가장 정확)**\n",
    "같은 아파트 단지라면 동이나 층이 달라도 건폐율은 동일합니다. 데이터 내에 이름이 같은 아파트가 있다면, 결측치가 없는 다른 행의 건폐율을 그대로 가져와 채우는 방법이 가장 안전합니다.\n",
    "* **2단계: '지역3(동)' + '단지규모'가 유사한 그룹의 중앙값 활용**\n",
    "동일 단지 데이터가 없다면, 같은 행정동(`지역3`) 내에서 아파트 세대수(`단지규모`)가 비슷한 주변 아파트들의 중앙값(median)으로 채우는 방식입니다. 지역 조례에 따라 법정 건폐율 제한이 유사하기 때문입니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "73bf57e9-b3c6-4ed5-9a2d-588860b2a792",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "건폐율 열의 남은 결측치 개수: 0\n",
      "처리 후 건폐율 요약 통계:\n",
      " count    23681.000000\n",
      "mean         0.248766\n",
      "std          0.106596\n",
      "min          0.017600\n",
      "25%          0.190100\n",
      "50%          0.223100\n",
      "75%          0.269900\n",
      "max          1.131600\n",
      "Name: 건폐율, dtype: float64\n"
     ]
    }
   ],
   "source": [
    "# [준비] 계산을 위해 건폐율 열을 숫자형(float)으로 변환합니다. (문자열이 섞여있을 경우 대비)\n",
    "df['건폐율'] = pd.to_numeric(df['건폐율'], errors='coerce')\n",
    "\n",
    "# 1단계: 동일한 '제목(아파트명)'을 가진 매물들의 건폐율 평균값으로 채우기\n",
    "# (같은 단지라면 건폐율이 같으므로 결측치가 없는 행의 값을 가져오게 됩니다)\n",
    "df['건폐율'] = df['건폐율'].fillna(df.groupby('제목')['건폐율'].transform('mean'))\n",
    "\n",
    "# 2단계: 여전히 남은 결측치는 같은 동('지역3')의 건폐율 중앙값(median)으로 채우기\n",
    "# (주변 지역의 유사한 토지 용도를 반영하기 위함입니다)\n",
    "df['건폐율'] = df['건폐율'].fillna(df.groupby('지역3')['건폐율'].transform('median'))\n",
    "\n",
    "# 3단계: 전체 지역 평균으로 최종 보완 (특정 동 전체에 데이터가 없는 경우 대비)\n",
    "global_median = df['건폐율'].median()\n",
    "df['건폐율'] = df['건폐율'].fillna(global_median)\n",
    "\n",
    "# 결측치 처리 결과 확인\n",
    "print(\"건폐율 열의 남은 결측치 개수:\", df['건폐율'].isnull().sum())\n",
    "print(\"처리 후 건폐율 요약 통계:\\n\", df['건폐율'].describe())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be32f058-1a48-4512-aa98-13613d26a706",
   "metadata": {},
   "source": [
    "### 현관구조 결측치 채우기\n",
    "\n",
    "* **1단계: 동일 단지(아파트명)의 데이터 활용 (가장 정확)**\n",
    "대부분의 아파트 단지는 단지 전체가 동일한 현관구조를 가지거나, 평형대별로 명확히 나뉩니다. 데이터 내에 동일한 아파트(`제목`)를 찾아 결측치가 없는 다른 매물의 현관구조(최빈값)를 그대로 가져와 채우는 방식입니다.\n",
    "* **2단계: '지역3(동)' + '면적2'가 유사한 그룹의 최빈값 활용**\n",
    "동일 단지 데이터가 없다면, 같은 동(`지역3`) 안에서 `면적2`가 비슷한 주변 매물들의 가장 흔한 현관구조(**최빈값**)를 찾아 채웁니다. 면적이 작으면 복도식, 넓으면 계단식일 확률이 높기 때문입니다.\n",
    "* **3단계: 전체 데이터의 최빈값으로 최종 보완**\n",
    "위의 두 방법으로도 채워지지 않는 데이터는 전체 부동산 데이터에서 가장 널리 쓰이는 현관구조(일반적으로 계단식)로 채워 마무리합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "db3ee16b-5524-4ff1-89a3-32322d4d5b88",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "현관구조 열의 남은 결측치 개수: 0\n",
      "\n",
      "--- 처리 후 현관구조 분포 ---\n",
      "현관구조\n",
      "계단식         18305\n",
      "복도식          4746\n",
      "중앙코어형         260\n",
      "계단,복도식        175\n",
      "-             163\n",
      "계단,중앙코어형       17\n",
      "기타             15\n",
      "Name: count, dtype: int64\n"
     ]
    }
   ],
   "source": [
    "# 최빈값을 구하는 함수 정의 (그룹 내에 데이터가 없을 경우를 대비한 예외 처리 포함)\n",
    "def get_group_mode(series):\n",
    "    mode_val = series.mode()\n",
    "    return mode_val.iloc[0] if not mode_val.empty else None\n",
    "\n",
    "# 1단계: 동일한 '제목(아파트명)'을 가진 매물들의 현관구조 최빈값으로 채우기\n",
    "df['현관구조'] = df['현관구조'].fillna(df.groupby('제목')['현관구조'].transform(get_group_mode))\n",
    "\n",
    "# 2단계: 여전히 남은 결측치는 같은 동('지역3') 내에서 면적을 10m² 단위로 묶어 해당 그룹의 최빈값으로 채우기\n",
    "# (면적대별로 계단식과 복도식이 나뉘는 경향을 반영합니다)\n",
    "df['면적_구간'] = (df['면적2'] // 10) * 10\n",
    "df['현관구조'] = df['현관구조'].fillna(df.groupby(['지역3', '면적_구간'])['현관구조'].transform(get_group_mode))\n",
    "\n",
    "# 3단계: 전체 데이터에서 가장 많이 등장하는 현관구조(최빈값)로 최종 보완\n",
    "global_mode = df['현관구조'].mode().iloc[0]\n",
    "df['현관구조'] = df['현관구조'].fillna(global_mode)\n",
    "\n",
    "# 사용 완료한 임시 열 삭제\n",
    "df = df.drop(columns=['면적_구간'])\n",
    "\n",
    "# 결측치 처리 결과 및 분포 확인\n",
    "print(\"현관구조 열의 남은 결측치 개수:\", df['현관구조'].isnull().sum())\n",
    "print(\"\\n--- 처리 후 현관구조 분포 ---\")\n",
    "print(df['현관구조'].value_counts())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "4c9cc7cb-90be-4eac-9617-972d5c35afd6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "제목             0\n",
      "소재지            0\n",
      "지역1            0\n",
      "지역2            0\n",
      "지역3            0\n",
      "지역4            0\n",
      "매매희망가격         0\n",
      "건설회사           0\n",
      "면적1            0\n",
      "면적2            0\n",
      "방수             0\n",
      "욕실수            0\n",
      "건폐율            0\n",
      "방향             0\n",
      "현관구조           0\n",
      "취득세            0\n",
      "중개보수           0\n",
      "해당층/총층        23\n",
      "난방연료/방식       23\n",
      "단지규모          23\n",
      "대지지분       21997\n",
      "입주일           34\n",
      "주차대수          34\n",
      "매물특징        1412\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "print(df.isnull().sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "654b05bf-c60b-41d1-aacc-15b6c3f06c2d",
   "metadata": {},
   "source": [
    "1) 텍스트 분리 및 숫자 정제,\n",
    "2) 총층 결측치 추정,\n",
    "3) 해당층 역산 및 예외 처리"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ee2a92b4-69dd-4b71-ae50-2edc5e8c079d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "해당층 남은 결측치: 0개 / 총층 남은 결측치: 0개\n",
      "\n",
      "--- 정제 완료 데이터 상위 5개 행 ---\n",
      "    해당층/총층  해당층  총층\n",
      "0     중층/-   12  23\n",
      "1  중층/총18층    9  18\n",
      "2  9층/총13층    9  13\n",
      "3      NaN   10  19\n",
      "4  3층/총14층    3  14\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# ==========================================\n",
    "# 단계 1: '해당층/총층' 텍스트 분리 및 숫자형 변환\n",
    "# ==========================================\n",
    "# '/' 기준으로 분리하여 각각 '층', '총' 글자를 제거하고 정수형으로 변환합니다.\n",
    "split_df = df['해당층/총층'].str.split('/', expand=True)\n",
    "\n",
    "df['해당층'] = pd.to_numeric(split_df[0].str.replace('층', '', regex=False), errors='coerce')\n",
    "df['총층'] = pd.to_numeric(split_df[1].str.replace('총', '', regex=False).str.replace('층', '', regex=False), errors='coerce')\n",
    "\n",
    "# ==========================================\n",
    "# 단계 2: '총층' 결측치 순차적 추정 (동일단지 -> 동일지역/건설사 -> 지역중앙값)\n",
    "# ==========================================\n",
    "# 1단계: 동일 아파트('제목') 그룹의 총층 중앙값\n",
    "df['총층'] = df['총층'].fillna(df.groupby('제목')['총층'].transform('median'))\n",
    "\n",
    "# 2단계: 동일 동('지역3') 내 같은 건설회사 그룹의 총층 중앙값\n",
    "if '건설회사' in df.columns:\n",
    "    df['총층'] = df['총층'].fillna(df.groupby(['지역3', '건설회사'])['총층'].transform('median'))\n",
    "\n",
    "# 3단계: 동일 동('지역3') 전체의 총층 중앙값\n",
    "df['총층'] = df['총층'].fillna(df.groupby('지역3')['총층'].transform('median'))\n",
    "\n",
    "# 4단계: 전체 데이터의 총층 중앙값으로 최종 보완\n",
    "global_total_median = df['총층'].median()\n",
    "df['총층'] = df['총층'].fillna(global_total_median)\n",
    "\n",
    "# [논리 보정 1] 총층은 최소한 이미 알고 있는 '해당층'보다는 크거나 같아야 합니다.\n",
    "df['총층'] = np.where((df['총층'] < df['해당층']) & df['해당층'].notnull(), df['해당층'], df['총층'])\n",
    "\n",
    "# ==========================================\n",
    "# 단계 3: '해당층' 결측치 역산 (저층/중층/고층 텍스트 활용)\n",
    "# ==========================================\n",
    "is_null_floor = df['해당층'].isnull()\n",
    "\n",
    "# 원본 문자열 기반으로 저/중/고 위치 조건을 판단합니다.\n",
    "is_low = df['해당층/총층'].str.contains('저층', na=False)\n",
    "is_mid = df['해당층/총층'].str.contains('중층', na=False)\n",
    "is_high = df['해당층/총층'].str.contains('고층', na=False)\n",
    "\n",
    "# 각 위치별 비율을 총층에 곱하여 해당층을 역산합니다 (반올림 처리)\n",
    "df.loc[is_null_floor & is_low, '해당층'] = (df['총층'] * 0.15).round().clip(lower=1)\n",
    "df.loc[is_null_floor & is_high, '해당층'] = (df['총층'] * 0.85).round()\n",
    "df.loc[is_null_floor & is_mid, '해당층'] = (df['총층'] * 0.50).round()\n",
    "\n",
    "# 텍스트조차 없어서 남은 완전 결측치는 대세인 중층(50%) 지점으로 채웁니다.\n",
    "df['해당층'] = df['해당층'].fillna((df['총층'] * 0.50).round())\n",
    "\n",
    "# [논리 보정 2] 계산 과정에서 해당층이 총층을 넘어가는 오류가 발생하면 총층값으로 제한합니다.\n",
    "df['해당층'] = np.where(df['해당층'] > df['총층'], df['총층'], df['해당층'])\n",
    "\n",
    "# ==========================================\n",
    "# [수정] 소수점 데이터 반올림 후 정수형(Int64) 변환\n",
    "# ==========================================\n",
    "# 계산 과정에서 생긴 소수점을 round()로 정리한 뒤 변환하여 TypeError를 방지합니다.\n",
    "df['해당층'] = df['해당층'].round().astype('Int64')\n",
    "df['총층'] = df['총층'].round().astype('Int64')\n",
    "\n",
    "# ==========================================\n",
    "# 최종 정제 결과 확인\n",
    "# ==========================================\n",
    "print(f\"해당층 남은 결측치: {df['해당층'].isnull().sum()}개 / 총층 남은 결측치: {df['총층'].isnull().sum()}개\")\n",
    "print(\"\\n--- 정제 완료 데이터 상위 5개 행 ---\")\n",
    "print(df[['해당층/총층', '해당층', '총층']].head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "d0af19b5-a70f-4b23-9e7f-64236059fbfd",
   "metadata": {},
   "outputs": [],
   "source": [
    "del df['해당층/총층']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "1a5e3457-7dca-4fdb-90e4-cf0d6374f96d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "제목             0\n",
      "소재지            0\n",
      "지역1            0\n",
      "지역2            0\n",
      "지역3            0\n",
      "지역4            0\n",
      "매매희망가격         0\n",
      "건설회사           0\n",
      "면적1            0\n",
      "면적2            0\n",
      "방수             0\n",
      "욕실수            0\n",
      "건폐율            0\n",
      "방향             0\n",
      "현관구조           0\n",
      "취득세            0\n",
      "중개보수           0\n",
      "난방연료/방식       23\n",
      "단지규모          23\n",
      "대지지분       21997\n",
      "입주일           34\n",
      "주차대수          34\n",
      "매물특징        1412\n",
      "해당층            0\n",
      "총층             0\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "print(df.isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "f010ff00-e2f9-4f04-b69f-28e5f5955249",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "난방연료 최종 결측치 개수: 0\n",
      "난방방식 최종 결측치 개수: 0\n",
      "\n",
      "--- 정제 후 난방연료 분포 ---\n",
      "난방연료\n",
      "도시가스    16642\n",
      "열병합      6831\n",
      "폐열        135\n",
      "기름         41\n",
      "기타         17\n",
      "LPG        15\n",
      "Name: count, dtype: int64\n",
      "\n",
      "--- 정제 후 난방방식 분포 ---\n",
      "난방방식\n",
      "개별난방    13414\n",
      "지역난방     7926\n",
      "중앙난방     2339\n",
      "기타          2\n",
      "Name: count, dtype: int64\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# 1. '난방연료/방식' 열을 ' / ' 기준으로 분리하여 임시 데이터프레임을 생성합니다.\n",
    "split_heating = df['난방연료/방식'].str.split('/', expand=True)\n",
    "\n",
    "# 2. 각 열을 생성하고 문자열 앞뒤 공백을 제거(.str.strip())하여 대입합니다.\n",
    "df['난방연료'] = split_heating[0].str.strip()\n",
    "df['난방방식'] = split_heating[1].str.strip()\n",
    "\n",
    "# [추가] '-'나 공백 문자열('')을 판다스 결측치(np.nan)로 강제 치환합니다.\n",
    "# 이렇게 변환해 두어야 fillna()와 groupby().transform()이 정상 작동합니다.\n",
    "df['난방연료'] = df['난방연료'].replace(['-', ''], np.nan)\n",
    "df['난방방식'] = df['난방방식'].replace(['-', ''], np.nan)\n",
    "\n",
    "# 최빈값을 안전하게 구해주는 람다 함수 정의 (그룹 내 데이터가 없을 경우 NaN 반환)\n",
    "get_mode = lambda x: x.mode().iloc[0] if not x.mode().empty else None\n",
    "\n",
    "# 3. [난방연료 결측치 채우기]\n",
    "# 1단계: 동일한 아파트('제목')를 가진 다른 매물들의 난방연료 최빈값으로 채우기\n",
    "df['난방연료'] = df['난방연료'].fillna(df.groupby('제목')['난방연료'].transform(get_mode))\n",
    "# 2단계: 동일 단지가 없는 경우, 서울시 전체에서 가장 흔한 '도시가스'로 최종 보완\n",
    "df['난방연료'] = df['난방연료'].fillna('도시가스')\n",
    "\n",
    "# 4. [난방방식 결측치 채우기]\n",
    "# 1단계: 동일한 아파트('제목')를 가진 다른 매물들의 난방방식 최빈값으로 채우기\n",
    "df['난방방식'] = df['난방방식'].fillna(df.groupby('제목')['난방방식'].transform(get_mode))\n",
    "# 2단계: 동일 단지가 없는 경우, 전체 데이터의 난방방식 최빈값으로 최종 보완\n",
    "global_heating_mode = df['난방방식'].mode().iloc[0] if not df['난방방식'].mode().empty else '개별난방'\n",
    "df['난방방식'] = df['난방방식'].fillna(global_heating_mode)\n",
    "\n",
    "# 5. 결과 확인을 위해 결측치 개수와 고유값 분포를 출력합니다.\n",
    "print(\"난방연료 최종 결측치 개수:\", df['난방연료'].isnull().sum())\n",
    "print(\"난방방식 최종 결측치 개수:\", df['난방방식'].isnull().sum())\n",
    "print(\"\\n--- 정제 후 난방연료 분포 ---\")\n",
    "print(df['난방연료'].value_counts())\n",
    "print(\"\\n--- 정제 후 난방방식 분포 ---\")\n",
    "print(df['난방방식'].value_counts())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "46df8505-94b2-499f-8a27-3eacd99323ce",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "제목             0\n",
      "소재지            0\n",
      "지역1            0\n",
      "지역2            0\n",
      "지역3            0\n",
      "지역4            0\n",
      "매매희망가격         0\n",
      "건설회사           0\n",
      "면적1            0\n",
      "면적2            0\n",
      "방수             0\n",
      "욕실수            0\n",
      "건폐율            0\n",
      "방향             0\n",
      "현관구조           0\n",
      "취득세            0\n",
      "중개보수           0\n",
      "난방연료/방식       23\n",
      "단지규모          23\n",
      "대지지분       21997\n",
      "입주일           34\n",
      "주차대수          34\n",
      "매물특징        1412\n",
      "해당층            0\n",
      "총층             0\n",
      "난방연료           0\n",
      "난방방식           0\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "print(df.isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "daa93cd5-231c-4f81-9d7d-0aff6440c5bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "del df['난방연료/방식']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "db97fc44-9def-4b74-b7be-b346b197212a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "입주일 열의 최종 결측치 개수: 0\n"
     ]
    }
   ],
   "source": [
    "# 최빈값을 구하는 람다 함수 정의 (데이터가 없는 경우를 대비한 예외 처리 포함)\n",
    "get_mode = lambda x: x.mode().iloc[0] if not x.mode().empty else None\n",
    "\n",
    "# 1단계: 동일한 아파트('제목')를 가진 다른 매물들의 입주일 최빈값으로 채우기\n",
    "# (같은 단지라면 입주일이 일치하므로 가장 확실하고 정확한 방법입니다)\n",
    "df['입주일'] = df['입주일'].fillna(df.groupby('제목')['입주일'].transform(get_mode))\n",
    "\n",
    "# 2단계: 동일 단지 정보가 없는 경우, 같은 동('지역3') 내 다른 아파트들의 입주일 최빈값으로 채우기\n",
    "# (주변 지역의 개발 시기가 비슷하다는 특성을 반영합니다)\n",
    "df['입주일'] = df['입주일'].fillna(df.groupby('지역3')['입주일'].transform(get_mode))\n",
    "\n",
    "# 3단계: 전체 데이터에서 가장 많이 등장하는 입주일(최빈값)로 최종 보완\n",
    "if not df['입주일'].mode().empty:\n",
    "    global_date_mode = df['입주일'].mode().iloc[0]\n",
    "    df['입주일'] = df['입주일'].fillna(global_date_mode)\n",
    "\n",
    "# 결측치 처리 결과 확인\n",
    "print(\"입주일 열의 최종 결측치 개수:\", df['입주일'].isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "67250b37-f9a1-402d-83a6-074def363d6e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0     2006\n",
      "1     2002\n",
      "2     1983\n",
      "3     2002\n",
      "4     1979\n",
      "5     2011\n",
      "6     2003\n",
      "7     1993\n",
      "8     2003\n",
      "9     1997\n",
      "10    2006\n",
      "11    1979\n",
      "12    1983\n",
      "13    1976\n",
      "14    2004\n",
      "Name: 입주일, dtype: Int64\n",
      "\n",
      "최종 데이터 타입: Int64\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "# 1. 기존 데이터 정제를 위해 문자열 타입으로 변환 및 공백 제거\n",
    "df['입주일_정제'] = df['입주일'].astype(str).str.strip()\n",
    "\n",
    "def clean_date_string(date_str):\n",
    "    if pd.isna(date_str) or date_str == 'nan' or date_str == '':\n",
    "        return None\n",
    "    \n",
    "    parts = date_str.split('.')\n",
    "    \n",
    "    # 마침표가 없어 연도만 있는 경우 (예: '2005')\n",
    "    if len(parts) < 2:\n",
    "        if len(parts[0]) == 4 and parts[0].isdigit():\n",
    "            return f\"{parts[0]}-01-01\"\n",
    "        return None\n",
    "    \n",
    "    year = parts[0].strip()\n",
    "    \n",
    "    # 월 처리\n",
    "    month = parts[1].strip()\n",
    "    if len(month) == 1:\n",
    "        month = '0' + month\n",
    "    elif len(month) == 0:\n",
    "        month = '01'\n",
    "        \n",
    "    # 일 처리\n",
    "    if len(parts) > 2 and parts[2].strip() != '':\n",
    "        day = parts[2].strip()\n",
    "        if len(day) == 1:\n",
    "            day = '0' + day\n",
    "    else:\n",
    "        day = '01'\n",
    "        \n",
    "    return f\"{year}-{month}-{day}\"\n",
    "\n",
    "# 2. 정제 함수 적용 및 datetime 형식으로 임시 변환\n",
    "df['입주일_정제'] = df['입주일_정제'].apply(clean_date_string)\n",
    "temp_datetime = pd.to_datetime(df['입주일_정제'], errors='coerce')\n",
    "\n",
    "# 3. dt.year를 사용하여 '연도'만 추출한 뒤 원래 '입주일' 열에 대입\n",
    "# 정수형태로 표현하기 위해 Int64 타입을 지정합니다. (결측치가 있어도 정수형 유지가 가능합니다)\n",
    "df['입주일'] = temp_datetime.dt.year.astype('Int64')\n",
    "\n",
    "# 사용이 끝난 임시 열 제거\n",
    "df = df.drop(columns=['입주일_정제'])\n",
    "\n",
    "# 변환 결과 확인\n",
    "print(df['입주일'].head(15))\n",
    "print(\"\\n최종 데이터 타입:\", df['입주일'].dtype)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "efea0d4c-51e2-4bf0-8a34-7a18de77f4aa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "제목            0\n",
      "소재지           0\n",
      "지역1           0\n",
      "지역2           0\n",
      "지역3           0\n",
      "지역4           0\n",
      "매매희망가격        0\n",
      "건설회사          0\n",
      "면적1           0\n",
      "면적2           0\n",
      "방수            0\n",
      "욕실수           0\n",
      "건폐율           0\n",
      "방향            0\n",
      "현관구조          0\n",
      "취득세           0\n",
      "중개보수          0\n",
      "단지규모         23\n",
      "대지지분      21997\n",
      "입주일           0\n",
      "주차대수         34\n",
      "매물특징       1412\n",
      "해당층           0\n",
      "총층            0\n",
      "난방연료          0\n",
      "난방방식          0\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "print(df.isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "9c66377f-6f79-4a93-ab6e-d494b065a7a6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "               주차대수  총주차대수  가구당대수\n",
      "0    총154대(가구당1.1대)    154    1.1\n",
      "1  총1,013대(가구당1.3대)   1013    1.3\n",
      "2    총308대(가구당1.0대)    308    1.0\n",
      "3               NaN   3750    1.6\n",
      "4  총2,100대(가구당1.0대)   2100    1.0\n",
      "5  총1,056대(가구당1.2대)   1056    1.2\n",
      "6  총1,616대(가구당1.0대)   1616    1.0\n",
      "7    총517대(가구당0.6대)    517    0.6\n",
      "8     총52대(가구당1.1대)     52    1.1\n",
      "9  총2,085대(가구당1.3대)   2085    1.3\n",
      "\n",
      "총주차대수 타입: Int64\n",
      "가구당대수 타입: float64\n",
      "   총주차대수  가구당대수\n",
      "0    154    1.1\n",
      "1   1013    1.3\n",
      "2    308    1.0\n",
      "3   3750    1.6\n",
      "4   2100    1.0\n",
      "5   1056    1.2\n",
      "6   1616    1.0\n",
      "7    517    0.6\n",
      "8     52    1.1\n",
      "9   2085    1.3\n",
      "\n",
      "총주차대수 타입: Int64\n",
      "가구당대수 타입: float64\n"
     ]
    }
   ],
   "source": [
    "# 데이터 정제를 위해 문자열 타입으로 변환합니다.\n",
    "df['주차대수_정제'] = df['주차대수'].astype(str).str.strip()\n",
    "\n",
    "# 1. 정규표현식을 사용하여 숫자와 소수점만 추출합니다.\n",
    "# '총1,013대'에서 숫자만 추출하기 위해 쉼표(,)를 먼저 제거합니다.\n",
    "df['주차대수_정제'] = df['주차대수_정제'].str.replace(',', '', regex=False)\n",
    "\n",
    "# 총주차대수 추출: '총' 뒤에 나오는 숫자들을 추출합니다.\n",
    "df['총주차대수'] = df['주차대수_정제'].str.extract(r'총(\\d+)')\n",
    "df['총주차대수'] = pd.to_numeric(df['총주차대수'], errors='coerce')\n",
    "\n",
    "# 가구당대수 추출: '가구당' 뒤에 나오는 숫자와 소수점(\\d+\\.?\\d*)을 추출합니다.\n",
    "df['가구당대수'] = df['주차대수_정제'].str.extract(r'가구당(\\d+\\.?\\d*)')\n",
    "df['가구당대수'] = pd.to_numeric(df['가구당대수'], errors='coerce')\n",
    "\n",
    "# 2. 결측치 채우기 (동일 단지 기반 대체)\n",
    "# 같은 아파트 단지('제목')라면 주차장 규모가 같으므로, 그룹별 중앙값(median)을 활용합니다.\n",
    "df['총주차대수'] = df['총주차대수'].fillna(df.groupby('제목')['총주차대수'].transform('median'))\n",
    "df['가구당대수'] = df['가구당대수'].fillna(df.groupby('제목')['가구당대수'].transform('median'))\n",
    "\n",
    "# 3. 예외 처리: 동일 단지 정보가 없어 여전히 남은 결측치는 같은 동('지역3')의 중앙값으로 채우기\n",
    "df['총주차대수'] = df['총주차대수'].fillna(df.groupby('지역3')['총주차대수'].transform('median'))\n",
    "df['가구당대수'] = df['가구당대수'].fillna(df.groupby('지역3')['가구당대수'].transform('median'))\n",
    "\n",
    "# 4. 최종 안전장치: 전체 데이터의 중앙값으로 최종 보완\n",
    "# 중앙값 계산 시 소수점이 발생할 수 있으므로, round()로 반올림한 후 Int64로 변환합니다.\n",
    "global_total_median = round(df['총주차대수'].median())\n",
    "df['총주차대수'] = df['총주차대수'].fillna(global_total_median).round().astype('Int64')\n",
    "\n",
    "global_per_median = df['가구당대수'].median()\n",
    "df['가구당대수'] = df['가구당대수'].fillna(global_per_median)\n",
    "\n",
    "# 변환 결과 확인\n",
    "print(df[['주차대수', '총주차대수', '가구당대수']].head(10))\n",
    "print(\"\\n총주차대수 타입:\", df['총주차대수'].dtype)\n",
    "print(\"가구당대수 타입:\", df['가구당대수'].dtype)\n",
    "\n",
    "# 사용이 끝난 임시 열 제거\n",
    "df = df.drop(columns=['주차대수', '주차대수_정제'])\n",
    "\n",
    "# 변환 결과 확인\n",
    "print(df[['총주차대수', '가구당대수']].head(10))\n",
    "print(\"\\n총주차대수 타입:\", df['총주차대수'].dtype)\n",
    "print(\"가구당대수 타입:\", df['가구당대수'].dtype)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "b5223aae-7198-4d69-be1f-33d3b414f619",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "제목            0\n",
      "소재지           0\n",
      "지역1           0\n",
      "지역2           0\n",
      "지역3           0\n",
      "지역4           0\n",
      "매매희망가격        0\n",
      "건설회사          0\n",
      "면적1           0\n",
      "면적2           0\n",
      "방수            0\n",
      "욕실수           0\n",
      "건폐율           0\n",
      "방향            0\n",
      "현관구조          0\n",
      "취득세           0\n",
      "중개보수          0\n",
      "단지규모         23\n",
      "대지지분      21997\n",
      "입주일           0\n",
      "매물특징       1412\n",
      "해당층           0\n",
      "총층            0\n",
      "난방연료          0\n",
      "난방방식          0\n",
      "총주차대수         0\n",
      "가구당대수         0\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "print(df.isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "d884d1f1-4e24-43a2-94fd-4e945d7ded9b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "대지지분 열의 최종 결측치 개수: 0\n",
      "\n",
      "--- 정제 후 대지지분 통계 정보 ---\n",
      "count    23681.000000\n",
      "mean        65.577700\n",
      "std         27.493060\n",
      "min          0.000000\n",
      "25%         45.817493\n",
      "50%         64.370839\n",
      "75%         80.510000\n",
      "max        305.945490\n",
      "Name: 대지지분, dtype: float64\n"
     ]
    }
   ],
   "source": [
    "# 1. '대지지분' 열이 문자열 형태이므로, 단위('㎡')와 대시('-')를 먼저 지워줍니다.\n",
    "# 공백을 제거하고 문자를 빈칸으로 대체합니다.\n",
    "df['대지지분_정제'] = df['대지지분'].astype(str).str.replace('㎡', '', regex=False)\n",
    "df['대지지분_정제'] = df['대지지분_정제'].str.replace('-', '', regex=False).str.strip()\n",
    "\n",
    "# 2. 단위를 지운 정제 열을 숫자형으로 안전하게 변환합니다.\n",
    "df['대지지분_정제'] = pd.to_numeric(df['대지지분_정제'], errors='coerce')\n",
    "\n",
    "# 3. 각 매물의 '면적2' 대비 '대지지분_정제'의 비율을 계산하여 임시 열로 만듭니다.\n",
    "df['_지분비율'] = df['대지지분_정제'] / df['면적2']\n",
    "\n",
    "# 4. 1단계: 동일한 아파트('제목')를 가진 매물들의 지분비율 평균값을 구합니다.\n",
    "# 그 후, 현재 매물의 '면적2'에 해당 비율을 곱해서 대지지분을 역산합니다.\n",
    "group_ratio_title = df.groupby('제목')['_지분비율'].transform('mean')\n",
    "df['대지지분_정제'] = df['대지지분_정제'].fillna(df['면적2'] * group_ratio_title)\n",
    "\n",
    "# 5. 2단계: 동일 단지 정보가 없는 경우, 같은 동('지역3')의 평균 지분비율을 적용하여 역산합니다.\n",
    "group_ratio_region = df.groupby('지역3')['_지분비율'].transform('mean')\n",
    "df['대지지분_정제'] = df['대지지분_정제'].fillna(df['면적2'] * group_ratio_region)\n",
    "\n",
    "# 6. 3단계: 최종 예외 처리 (전체 데이터의 평균 지분비율을 적용)\n",
    "global_ratio = df['_지분비율'].mean()\n",
    "if pd.isna(global_ratio) or global_ratio == 0:\n",
    "    # 혹시 모를 전체 결측치 상황을 대비한 안전장치 (일반적인 아파트 전용면적 대비 대지지분 비율 약 0.5 대입)\n",
    "    global_ratio = 0.5\n",
    "df['대지지분_정제'] = df['대지지분_정제'].fillna(df['면적2'] * global_ratio)\n",
    "\n",
    "# 7. 정제가 끝난 데이터를 원본 '대지지분' 열에 덮어씌우고 임시 열들을 제거합니다.\n",
    "df['대지지분'] = df['대지지분_정제']\n",
    "df = df.drop(columns=['대지지분_정제', '_지분비율'])\n",
    "\n",
    "# 결측치 처리 결과 및 통계 정보 재확인\n",
    "print(\"대지지분 열의 최종 결측치 개수:\", df['대지지분'].isnull().sum())\n",
    "print(\"\\n--- 정제 후 대지지분 통계 정보 ---\")\n",
    "print(df['대지지분'].describe())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "7720300a-93e2-49b2-a2e1-87abc0ea6b7a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "제목           0\n",
      "소재지          0\n",
      "지역1          0\n",
      "지역2          0\n",
      "지역3          0\n",
      "지역4          0\n",
      "매매희망가격       0\n",
      "건설회사         0\n",
      "면적1          0\n",
      "면적2          0\n",
      "방수           0\n",
      "욕실수          0\n",
      "건폐율          0\n",
      "방향           0\n",
      "현관구조         0\n",
      "취득세          0\n",
      "중개보수         0\n",
      "단지규모        23\n",
      "대지지분         0\n",
      "입주일          0\n",
      "매물특징      1412\n",
      "해당층          0\n",
      "총층           0\n",
      "난방연료         0\n",
      "난방방식         0\n",
      "총주차대수        0\n",
      "가구당대수        0\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "print(df.isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "a1ad3807-392c-4676-a6e4-70237fb57928",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   동수   가구수\n",
      "0   3   134\n",
      "1  13   801\n",
      "2   3   308\n",
      "3  37  2176\n",
      "4  21  2100\n",
      "5  13   867\n",
      "6  20  1559\n",
      "7  10   855\n",
      "8   1    49\n",
      "9  15  1592\n",
      "\n",
      "동수 데이터 타입: Int64\n",
      "가구수 데이터 타입: Int64\n"
     ]
    }
   ],
   "source": [
    "# 1. 가구수의 쉼표(',')를 먼저 제거하고 공백을 정리합니다.\n",
    "df['단지규모_정제'] = df['단지규모'].astype(str).str.replace(',', '', regex=False).str.strip()\n",
    "\n",
    "# 2. 정규표현식을 사용하여 숫자 데이터만 추출합니다.\n",
    "# '총 X 개동' 에서 숫자 X를 추출\n",
    "df['동수'] = df['단지규모_정제'].str.extract(r'총\\s*(\\d+)\\s*개동')\n",
    "df['동수'] = pd.to_numeric(df['동수'], errors='coerce')\n",
    "\n",
    "# 'X가구' 에서 숫자 X를 추출\n",
    "df['가구수'] = df['단지규모_정제'].str.extract(r'(\\d+)\\s*가구')\n",
    "df['가구수'] = pd.to_numeric(df['가구수'], errors='coerce')\n",
    "\n",
    "# 3. 결측치 채우기 (동일 단지 기반 대체)\n",
    "# 같은 아파트 단지('제목')라면 동수와 가구수가 일치하므로 그룹별 중앙값(median)을 활용합니다.\n",
    "df['동수'] = df['동수'].fillna(df.groupby('제목')['동수'].transform('median'))\n",
    "df['가구수'] = df['가구수'].fillna(df.groupby('제목')['가구수'].transform('median'))\n",
    "\n",
    "# 4. 예외 처리: 동일 단지 정보가 없어 여전히 남은 결측치는 같은 동('지역3')의 중앙값으로 채우기\n",
    "df['동수'] = df['동수'].fillna(df.groupby('지역3')['동수'].transform('median'))\n",
    "df['가구수'] = df['가구수'].fillna(df.groupby('지역3')['가구수'].transform('median'))\n",
    "\n",
    "# 5. 최종 안전장치: 전체 데이터의 중앙값으로 채우고 소수점 올림 처리 후 정수형(Int64) 변환\n",
    "global_dong_median = round(df['동수'].median()) if not pd.isna(df['동수'].median()) else 1\n",
    "global_gagu_median = round(df['가구수'].median()) if not pd.isna(df['가구수'].median()) else 100\n",
    "\n",
    "df['동수'] = df['동수'].fillna(global_dong_median).round().astype('Int64')\n",
    "df['가구수'] = df['가구수'].fillna(global_gagu_median).round().astype('Int64')\n",
    "\n",
    "# 사용이 끝난 임시 열 제거\n",
    "df = df.drop(columns=['단지규모', '단지규모_정제'])\n",
    "\n",
    "# 변환 결과 확인\n",
    "print(df[['동수', '가구수']].head(10))\n",
    "print(\"\\n동수 데이터 타입:\", df['동수'].dtype)\n",
    "print(\"가구수 데이터 타입:\", df['가구수'].dtype)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "75717f0e-b1bb-45b4-9b08-c99e60e7bcc7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "제목           0\n",
      "소재지          0\n",
      "지역1          0\n",
      "지역2          0\n",
      "지역3          0\n",
      "지역4          0\n",
      "매매희망가격       0\n",
      "건설회사         0\n",
      "면적1          0\n",
      "면적2          0\n",
      "방수           0\n",
      "욕실수          0\n",
      "건폐율          0\n",
      "방향           0\n",
      "현관구조         0\n",
      "취득세          0\n",
      "중개보수         0\n",
      "대지지분         0\n",
      "입주일          0\n",
      "매물특징      1412\n",
      "해당층          0\n",
      "총층           0\n",
      "난방연료         0\n",
      "난방방식         0\n",
      "총주차대수        0\n",
      "가구당대수        0\n",
      "동수           0\n",
      "가구수          0\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "print(df.isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "09eaf356-64ce-4876-b511-64847841ada5",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df.drop(columns=['제목', '매물특징'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "5102aae5-6a71-42ed-9618-9c152cef7504",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.to_excel('data/r114_전처리결과.xlsx')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35d981f7-e220-4a5c-a81a-f1bed8f9eeff",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "54321918-5d9a-4b16-886b-ced544e933f1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'pandas.DataFrame'>\n",
      "RangeIndex: 23681 entries, 0 to 23680\n",
      "Data columns (total 27 columns):\n",
      " #   Column      Non-Null Count  Dtype  \n",
      "---  ------      --------------  -----  \n",
      " 0   Unnamed: 0  23681 non-null  int64  \n",
      " 1   소재지         23681 non-null  str    \n",
      " 2   지역1         23681 non-null  str    \n",
      " 3   지역2         23681 non-null  str    \n",
      " 4   지역3         23681 non-null  str    \n",
      " 5   지역4         23681 non-null  str    \n",
      " 6   매매희망가격      23681 non-null  int64  \n",
      " 7   건설회사        23681 non-null  str    \n",
      " 8   면적1         23681 non-null  float64\n",
      " 9   면적2         23681 non-null  float64\n",
      " 10  방수          23681 non-null  int64  \n",
      " 11  욕실수         23681 non-null  int64  \n",
      " 12  건폐율         23681 non-null  float64\n",
      " 13  방향          23681 non-null  str    \n",
      " 14  현관구조        23681 non-null  str    \n",
      " 15  취득세         23681 non-null  int64  \n",
      " 16  중개보수        23681 non-null  int64  \n",
      " 17  대지지분        23681 non-null  float64\n",
      " 18  입주일         23681 non-null  int64  \n",
      " 19  해당층         23681 non-null  int64  \n",
      " 20  총층          23681 non-null  int64  \n",
      " 21  난방연료        23681 non-null  str    \n",
      " 22  난방방식        23681 non-null  str    \n",
      " 23  총주차대수       23681 non-null  int64  \n",
      " 24  가구당대수       23681 non-null  float64\n",
      " 25  동수          23681 non-null  int64  \n",
      " 26  가구수         23681 non-null  int64  \n",
      "dtypes: float64(5), int64(12), str(10)\n",
      "memory usage: 4.9 MB\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "df = pd.read_excel('data/r114_전처리결과.xlsx')\n",
    "print(df.info())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "dee1ac19-e373-4161-9d13-b31e99a58e56",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "건설회사\n",
       "삼성물산           2755\n",
       "현대건설           1857\n",
       "대림산업           1288\n",
       "대우건설           1233\n",
       "현대산업개발         1148\n",
       "               ... \n",
       "미봉                1\n",
       "태조건설              1\n",
       "청광종합건설 청광건설       1\n",
       "에이스종합건설           1\n",
       "우당종합개발            1\n",
       "Name: count, Length: 495, dtype: int64"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df['건설회사'].value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "1d385d61-5f59-4890-872b-9130328ec004",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "건설회사\n",
      "삼성물산        2755\n",
      "현대건설        1857\n",
      "대림산업        1288\n",
      "대우건설        1233\n",
      "현대산업개발      1148\n",
      "한국토지주택공사    1018\n",
      "우성건설         762\n",
      "롯데건설         700\n",
      "한신공영         656\n",
      "두산건설         568\n",
      "GS건설         543\n",
      "삼호           426\n",
      "쌍용건설         395\n",
      "벽산건설         344\n",
      "경남기업         339\n",
      "극동건설         322\n",
      "동부건설         322\n",
      "신동아건설        302\n",
      "삼익건설         296\n",
      "한진중공업        294\n",
      "금호건설         287\n",
      "라이프주택개발      269\n",
      "SK건설         250\n",
      "포스코건설        248\n",
      "LIG건설        247\n",
      "동아건설         239\n",
      "도시개발공사       233\n",
      "중앙건설         209\n",
      "풍림산업         195\n",
      "한양           195\n",
      "청구           194\n",
      "삼환기업         185\n",
      "한화건설         135\n",
      "태영건설         130\n",
      "이수건설         127\n",
      "SH공사         125\n",
      "코오롱건설        119\n",
      "남광토건         119\n",
      "한보종합건설       119\n",
      "삼성중공업        116\n",
      "성원건설         115\n",
      "서울시도시개발      114\n",
      "동성건설         111\n",
      "한일건설         108\n",
      "월드건설         107\n",
      "Name: count, dtype: int64\n"
     ]
    }
   ],
   "source": [
    "df_filtered = df[df['건설회사'].map(df['건설회사'].value_counts()) >= 100]\n",
    "print(df_filtered['건설회사'].value_counts())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "9437663b-e5c9-4182-b83b-30749fbdd2e6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "19864"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(df_filtered)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "b449cb09-bf14-41b7-b0ef-a56ff5a01901",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_filtered.to_excel('data/r114_전처리결과1.xlsx')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "460f4e90-82c3-42db-b53a-bd4a520590ae",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- 인코딩 후 생성된 열 목록 ---\n",
      "['난방연료_LPG', '난방연료_기름', '난방연료_기타', '난방연료_도시가스', '난방연료_열병합', '난방연료_폐열', '난방방식_개별난방', '난방방식_기타', '난방방식_중앙난방', '난방방식_지역난방']\n",
      "\n",
      "--- 인코딩 결과 데이터 확인 ---\n",
      "   난방연료_LPG  난방연료_기름  난방연료_기타  난방연료_도시가스  난방연료_열병합  난방연료_폐열  난방방식_개별난방  \\\n",
      "0         0        0        0          1         0        0          1   \n",
      "1         0        0        0          1         0        0          1   \n",
      "2         0        0        0          0         1        0          0   \n",
      "3         0        0        0          1         0        0          1   \n",
      "4         0        0        0          0         1        0          0   \n",
      "\n",
      "   난방방식_기타  난방방식_중앙난방  난방방식_지역난방  \n",
      "0        0          0          0  \n",
      "1        0          0          0  \n",
      "2        0          0          1  \n",
      "3        0          0          0  \n",
      "4        0          0          1  \n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "# 1. 원-핫 인코딩을 적용할 대상 열을 지정합니다.\n",
    "target_columns = ['난방연료', '난방방식']\n",
    "\n",
    "# 2. pd.get_dummies()를 사용하여 원-핫 인코딩을 수행합니다.\n",
    "# dtype=int를 지정하면 True/False 대신 1과 0으로 깔끔하게 출력됩니다.\n",
    "df_encoded = pd.get_dummies(df, columns=target_columns, dtype=int)\n",
    "\n",
    "# 3. 인코딩이 적용된 새로운 데이터프레임의 열 이름을 확인합니다.\n",
    "print(\"--- 인코딩 후 생성된 열 목록 ---\")\n",
    "print([col for col in df_encoded.columns if '난방연료' in col or '난방방식' in col])\n",
    "\n",
    "# 4. 상위 5개 행의 변환 결과를 확인합니다.\n",
    "print(\"\\n--- 인코딩 결과 데이터 확인 ---\")\n",
    "print(df_encoded[[col for col in df_encoded.columns if '난방연료' in col or '난방방식' in col]].head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "623a02f2-07c9-41d6-8f6a-332237051e31",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
