Spaces:
Sleeping
Sleeping
- preprocessing.py +36 -31
preprocessing.py
CHANGED
|
@@ -33,25 +33,25 @@ def convert_tide_level_to_residual(df, station_id):
|
|
| 33 |
else:
|
| 34 |
df['date'] = df['date'].dt.tz_convert(kst)
|
| 35 |
|
| 36 |
-
# 4.
|
| 37 |
-
|
| 38 |
-
start_time =
|
| 39 |
-
end_time =
|
| 40 |
-
print(f"📅
|
| 41 |
|
| 42 |
-
# 5. Supabase에서 harmonic_level 조회
|
| 43 |
try:
|
| 44 |
harmonic_data = get_harmonic_predictions(station_id, start_time, end_time)
|
| 45 |
print(f"📊 조화 예측 데이터 {len(harmonic_data) if harmonic_data else 0}개 조회")
|
| 46 |
|
| 47 |
if not harmonic_data:
|
| 48 |
print("⚠️ 조화 예측 데이터가 없습니다. 가상 데이터로 대체합니다.")
|
| 49 |
-
return create_mock_residual_data(
|
| 50 |
|
| 51 |
except Exception as e:
|
| 52 |
print(f"❌ Supabase 조회 오류: {e}")
|
| 53 |
print("⚠️ 가상 데이터로 대체합니다.")
|
| 54 |
-
return create_mock_residual_data(
|
| 55 |
|
| 56 |
# 6. harmonic_data를 딕셔너리로 변환 (시간 기준)
|
| 57 |
harmonic_dict = {}
|
|
@@ -80,11 +80,11 @@ def convert_tide_level_to_residual(df, station_id):
|
|
| 80 |
|
| 81 |
print(f"📊 사용 가능한 조화 데이터: {len(harmonic_dict)}개")
|
| 82 |
|
| 83 |
-
# 7. residual 계산
|
| 84 |
residual_values = []
|
| 85 |
successful_conversions = 0
|
| 86 |
|
| 87 |
-
for idx, row in
|
| 88 |
tide_level = row['tide_level']
|
| 89 |
timestamp = row['date']
|
| 90 |
|
|
@@ -101,8 +101,8 @@ def convert_tide_level_to_residual(df, station_id):
|
|
| 101 |
|
| 102 |
# 이상치 플래그 확인
|
| 103 |
is_outlier = False
|
| 104 |
-
if '_tide_outlier_flag' in
|
| 105 |
-
is_outlier =
|
| 106 |
|
| 107 |
if is_outlier:
|
| 108 |
# 이상치로 탐지된 경우 residual = 0 (harmonic만 사용)
|
|
@@ -117,18 +117,18 @@ def convert_tide_level_to_residual(df, station_id):
|
|
| 117 |
# 조화 데이터가 없으면 평균값으로 대체
|
| 118 |
residual_values.append(0.0)
|
| 119 |
|
| 120 |
-
# 8. residual 컬럼 추가
|
| 121 |
-
|
| 122 |
|
| 123 |
# 9. tide_level 컬럼 제거 (모델에서 사용하지 않음)
|
| 124 |
-
if 'tide_level' in
|
| 125 |
-
|
| 126 |
print("🗑️ tide_level 컬럼 제거 (변환 완료)")
|
| 127 |
|
| 128 |
-
conversion_rate = successful_conversions / len(
|
| 129 |
-
print(f"✅ 변환 완료: {successful_conversions}/{len(
|
| 130 |
|
| 131 |
-
return
|
| 132 |
|
| 133 |
def parse_time_string(time_str):
|
| 134 |
"""다양한 형태의 시간 문자열 파싱"""
|
|
@@ -697,7 +697,7 @@ def handle_missing_values(df, station_id=None):
|
|
| 697 |
def preprocess_uploaded_file(file_path, station_id):
|
| 698 |
"""
|
| 699 |
업로드된 파일의 전체 전처리 파이프라인
|
| 700 |
-
이상치 탐지 → 결측치 처리 → tide_level → residual 변환 + 검증
|
| 701 |
"""
|
| 702 |
try:
|
| 703 |
print(f"\n🚀 {station_id} 관측소 데이터 전처리 시작")
|
|
@@ -712,26 +712,31 @@ def preprocess_uploaded_file(file_path, station_id):
|
|
| 712 |
if not is_valid:
|
| 713 |
return None, f"입력 데이터 오류:\n" + "\n".join(issues)
|
| 714 |
|
| 715 |
-
# 3.
|
| 716 |
-
print("
|
|
|
|
|
|
|
| 717 |
|
| 718 |
-
#
|
| 719 |
-
|
|
|
|
|
|
|
|
|
|
| 720 |
if tide_outliers.any():
|
| 721 |
print(f"🌊 tide_level 이상치 {tide_outliers.sum()}개 → residual=0 처리 예정")
|
| 722 |
-
|
| 723 |
|
| 724 |
-
#
|
| 725 |
-
weather_outliers = detect_weather_outliers(
|
| 726 |
for col in weather_outliers.columns:
|
| 727 |
if weather_outliers[col].any():
|
| 728 |
print(f"🌡️ {col} 이상치 {weather_outliers[col].sum()}개 → NaN 변환")
|
| 729 |
-
|
| 730 |
|
| 731 |
-
#
|
| 732 |
-
df_cleaned = handle_missing_values(
|
| 733 |
|
| 734 |
-
#
|
| 735 |
converted_df = convert_tide_level_to_residual(df_cleaned, station_id)
|
| 736 |
|
| 737 |
# 5. 변환된 데이터를 임시 파일로 저장
|
|
|
|
| 33 |
else:
|
| 34 |
df['date'] = df['date'].dt.tz_convert(kst)
|
| 35 |
|
| 36 |
+
# 4. 입력 데이터는 이미 144개로 슬라이싱된 상태
|
| 37 |
+
df_input = df.copy()
|
| 38 |
+
start_time = df_input['date'].min()
|
| 39 |
+
end_time = df_input['date'].max()
|
| 40 |
+
print(f"📅 입력 데이터 시간 범위: {start_time} ~ {end_time}")
|
| 41 |
|
| 42 |
+
# 5. Supabase에서 harmonic_level 조회
|
| 43 |
try:
|
| 44 |
harmonic_data = get_harmonic_predictions(station_id, start_time, end_time)
|
| 45 |
print(f"📊 조화 예측 데이터 {len(harmonic_data) if harmonic_data else 0}개 조회")
|
| 46 |
|
| 47 |
if not harmonic_data:
|
| 48 |
print("⚠️ 조화 예측 데이터가 없습니다. 가상 데이터로 대체합니다.")
|
| 49 |
+
return create_mock_residual_data(df_input)
|
| 50 |
|
| 51 |
except Exception as e:
|
| 52 |
print(f"❌ Supabase 조회 오류: {e}")
|
| 53 |
print("⚠️ 가상 데이터로 대체합니다.")
|
| 54 |
+
return create_mock_residual_data(df_input)
|
| 55 |
|
| 56 |
# 6. harmonic_data를 딕셔너리로 변환 (시간 기준)
|
| 57 |
harmonic_dict = {}
|
|
|
|
| 80 |
|
| 81 |
print(f"📊 사용 가능한 조화 데이터: {len(harmonic_dict)}개")
|
| 82 |
|
| 83 |
+
# 7. residual 계산
|
| 84 |
residual_values = []
|
| 85 |
successful_conversions = 0
|
| 86 |
|
| 87 |
+
for idx, row in df_input.iterrows():
|
| 88 |
tide_level = row['tide_level']
|
| 89 |
timestamp = row['date']
|
| 90 |
|
|
|
|
| 101 |
|
| 102 |
# 이상치 플래그 확인
|
| 103 |
is_outlier = False
|
| 104 |
+
if '_tide_outlier_flag' in df_input.columns:
|
| 105 |
+
is_outlier = df_input.at[idx, '_tide_outlier_flag'] if not pd.isna(df_input.at[idx, '_tide_outlier_flag']) else False
|
| 106 |
|
| 107 |
if is_outlier:
|
| 108 |
# 이상치로 탐지된 경우 residual = 0 (harmonic만 사용)
|
|
|
|
| 117 |
# 조화 데이터가 없으면 평균값으로 대체
|
| 118 |
residual_values.append(0.0)
|
| 119 |
|
| 120 |
+
# 8. residual 컬럼 추가
|
| 121 |
+
df_input['residual'] = residual_values
|
| 122 |
|
| 123 |
# 9. tide_level 컬럼 제거 (모델에서 사용하지 않음)
|
| 124 |
+
if 'tide_level' in df_input.columns:
|
| 125 |
+
df_input = df_input.drop(columns=['tide_level'])
|
| 126 |
print("🗑️ tide_level 컬럼 제거 (변환 완료)")
|
| 127 |
|
| 128 |
+
conversion_rate = successful_conversions / len(df_input) * 100
|
| 129 |
+
print(f"✅ 변환 완료: {successful_conversions}/{len(df_input)} ({conversion_rate:.1f}%)")
|
| 130 |
|
| 131 |
+
return df_input
|
| 132 |
|
| 133 |
def parse_time_string(time_str):
|
| 134 |
"""다양한 형태의 시간 문자열 파싱"""
|
|
|
|
| 697 |
def preprocess_uploaded_file(file_path, station_id):
|
| 698 |
"""
|
| 699 |
업로드된 파일의 전체 전처리 파이프라인
|
| 700 |
+
슬라이싱 → 이상치 탐지 → 결측치 처리 → tide_level → residual 변환 + 검증
|
| 701 |
"""
|
| 702 |
try:
|
| 703 |
print(f"\n🚀 {station_id} 관측소 데이터 전처리 시작")
|
|
|
|
| 712 |
if not is_valid:
|
| 713 |
return None, f"입력 데이터 오류:\n" + "\n".join(issues)
|
| 714 |
|
| 715 |
+
# 3. 마지막 144개로 먼저 슬라이싱 (모델 입력 크기)
|
| 716 |
+
print(f"✂️ 마지막 144개 데이터로 슬라이싱 (모델 입력 크기)")
|
| 717 |
+
df_sliced = df.tail(144).copy()
|
| 718 |
+
print(f"📊 슬라이싱 후 데이터: {len(df_sliced)}행 × {len(df_sliced.columns)}열")
|
| 719 |
|
| 720 |
+
# 4. 이상치 탐지 및 처리 (144개만 대상)
|
| 721 |
+
print("\n🔍 이상치 탐지 및 처리 단계 (144개 데이터 기준)")
|
| 722 |
+
|
| 723 |
+
# 4-1. Harmonic 기반 tide_level 이상치 탐지
|
| 724 |
+
tide_outliers = detect_harmonic_based_outliers(df_sliced, station_id)
|
| 725 |
if tide_outliers.any():
|
| 726 |
print(f"🌊 tide_level 이상치 {tide_outliers.sum()}개 → residual=0 처리 예정")
|
| 727 |
+
df_sliced.loc[tide_outliers, '_tide_outlier_flag'] = True
|
| 728 |
|
| 729 |
+
# 4-2. 기상 데이터 물리적 한계 기반 이상치 탐지
|
| 730 |
+
weather_outliers = detect_weather_outliers(df_sliced)
|
| 731 |
for col in weather_outliers.columns:
|
| 732 |
if weather_outliers[col].any():
|
| 733 |
print(f"🌡️ {col} 이상치 {weather_outliers[col].sum()}개 → NaN 변환")
|
| 734 |
+
df_sliced.loc[weather_outliers[col], col] = np.nan
|
| 735 |
|
| 736 |
+
# 5. 결측치 처리
|
| 737 |
+
df_cleaned = handle_missing_values(df_sliced, station_id)
|
| 738 |
|
| 739 |
+
# 6. tide_level → residual 변환 (이상치 플래그 반영)
|
| 740 |
converted_df = convert_tide_level_to_residual(df_cleaned, station_id)
|
| 741 |
|
| 742 |
# 5. 변환된 데이터를 임시 파일로 저장
|