alwaysgood commited on
Commit
c1d663e
·
verified ·
1 Parent(s): 902ce76
Files changed (1) hide show
  1. 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. 마지막 144개 데이터만 사용 (슬라이싱 최적화)
37
- df_last_144 = df.tail(144).copy()
38
- start_time = df_last_144['date'].min()
39
- end_time = df_last_144['date'].max()
40
- print(f"📅 마지막 144개 데이터 시간 범위: {start_time} ~ {end_time}")
41
 
42
- # 5. Supabase에서 harmonic_level 조회 (144개만)
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_last_144)
50
 
51
  except Exception as e:
52
  print(f"❌ Supabase 조회 오류: {e}")
53
  print("⚠️ 가상 데이터로 대체합니다.")
54
- return create_mock_residual_data(df_last_144)
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 계산 (마지막 144개만)
84
  residual_values = []
85
  successful_conversions = 0
86
 
87
- for idx, row in df_last_144.iterrows():
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 df_last_144.columns:
105
- is_outlier = df_last_144.at[idx, '_tide_outlier_flag'] if not pd.isna(df_last_144.at[idx, '_tide_outlier_flag']) else False
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 컬럼 추가 (마지막 144개 데이터에만)
121
- df_last_144['residual'] = residual_values
122
 
123
  # 9. tide_level 컬럼 제거 (모델에서 사용하지 않음)
124
- if 'tide_level' in df_last_144.columns:
125
- df_last_144 = df_last_144.drop(columns=['tide_level'])
126
  print("🗑️ tide_level 컬럼 제거 (변환 완료)")
127
 
128
- conversion_rate = successful_conversions / len(df_last_144) * 100
129
- print(f"✅ 변환 완료: {successful_conversions}/{len(df_last_144)} ({conversion_rate:.1f}%)")
130
 
131
- return df_last_144
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("\n🔍 이상치 탐지 처리 단계")
 
 
717
 
718
- # 3-1. Harmonic 기반 tide_level 이상치 탐지
719
- tide_outliers = detect_harmonic_based_outliers(df, station_id)
 
 
 
720
  if tide_outliers.any():
721
  print(f"🌊 tide_level 이상치 {tide_outliers.sum()}개 → residual=0 처리 예정")
722
- df.loc[tide_outliers, '_tide_outlier_flag'] = True
723
 
724
- # 3-2. 기상 데이터 물리적 한계 기반 이상치 탐지
725
- weather_outliers = detect_weather_outliers(df)
726
  for col in weather_outliers.columns:
727
  if weather_outliers[col].any():
728
  print(f"🌡️ {col} 이상치 {weather_outliers[col].sum()}개 → NaN 변환")
729
- df.loc[weather_outliers[col], col] = np.nan
730
 
731
- # 4. 결측치 처리
732
- df_cleaned = handle_missing_values(df, station_id)
733
 
734
- # 5. tide_level → residual 변환 (이상치 플래그 반영)
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. 변환된 데이터를 임시 파일로 저장