Subscribe

📚 Project & TIL

Project [Personal]
2
SONTAROT
Project [Personal]
  • Jaenoo
Python
4
Basic Python
Python
  • Jaenoo
Advanced Python
Python
  • Jaenoo
Numpy
Python
  • Jaenoo
SQL
1
JAVA
3
Basic JAVA
JAVA
  • Jaenoo
Advanced JAVA
JAVA
  • Jaenoo
Scala
1

[EST soft]시계열프로젝트 #녹조현상

Status
Project [EST soft]
Assignee
  • Jaenoo
Created by
  • Jaenoo

Data & Code :

1. 프로젝트 개요

목표
낙동강 유역의 유해 남조류 세포 수를 시계열로 예측하는 AI 모델 개발
수질 및 환경 요인을 기반으로 녹조 발생 경보 시스템 기초 모델 구축
환경 정책·수질 관리·농업 및 양식업 대응에 활용 가능한 분석 프레임워크 제공
배경
남조류 과다 증식은 여름철에 심각한 수질 악화를 유발하며, 독소 생성 시 인체와 생태계에 위협
기존 연구들은 짧은 기간·단일 지역·제한된 지표만 사용 → 장기간, 다변량, 시계열 모델 필요

2. 데이터 구축 과정✨

요약
녹조 데이터셋의 어려운 점은, X(설명 변수, 수질 데이터셋)와 Y(목표 변수, 녹조 데이터셋) 두 데이터가 완전히 따로 놀아서, 결합하기 어렵다는 점.
X의 측정점은 낙동강에서만 260개가 넘고, Y의 측정점은 36개인데 둘은 완전 별개
가장 가능성 있는 결합 방법은, 위치에 기반해 가까운 지역에서 측정한 수질 정보와 녹조 정보를 공간 상에서 매칭하는 것
하지만 두 데이터셋이 측정 베이스임에도, 측정 지점을 전혀 공유하지 않음
심지어 주소나 구체적 위치 정보도 없음
이에, 각각의 측정 지점 정보를 다른 데이터셋 등에서 찾아 결합하고, 주소만 있는 경우는 지오코딩을 해 위치 정보를 붙여 넣어 X, Y 데이터셋을 완성
하지만 실폭하천 지도에 플로팅해보니... 녹조 발생 지역과 관계없는 X 지역도 많음을 확인.
원래 계획은 각 X 측정소 별로 가장 가까운 Y 녹조 측정소를 매칭하려 했지만,
이 정도면 가까운 지역만 추려서(거리 바탕으로) X 측정소를 한정한 뒤 진행하는 게 맞을 듯
아래는 10km 반경 지역 추출하는 모습

Part 1. 주요 녹조 관련 데이터 소스 탐색 및 결정

후보 데이터셋:

1.
물환경정보시스템 - 과거수질자료 -> 녹조 TGT(유해남조류세포수, 마이크로시스티스 등 포함).
https://water.nier.go.kr/web/algaePreMeasure?pMENU_NO=111
2.
물환경정보시스템 - 수질측정망 -> 60여 개 특성 존재
https://water.nier.go.kr/web/board/29/?pMENU_NO=572
3.
물환경정보시스템 - 자동측정망: 확정자료 조회 -> 2012년 이후 자료 존재. 엽록소 항목 참조 가능
https://water.nier.go.kr/web/autoMeasure/confirm?pMENU_NO=576
3은 1과 비슷하며, 최근 자료를 제공하지 않는다는 한계(미확정 예비자료가 존재)
따라서 이 가운데 1. 물환경정보시스템 - 과거수질자료2. 물환경정보시스템 - 수질측정망을 이용해 메인 데이터셋을 구축하기로.
이 때 전국을 모두 하기 어려우므로, 녹조 문제가 특히 심각한 낙동강을 목표 지역으로 설정
1이 녹조 관련 지표가 다수 있어 **목표 변수(Y)**로
2가 주요 수질 특성이 존재해 **설명 변수(X)**로 설정

Part 2. 데이터셋 병합

하지만 1과 2는 merge가 매우 까다로운 데이터라는 점이 문제
측정 장소는 강을 따라 공간상에 2차원으로 배치돼 있는데,
두 측정 자료의 측정 장소와 주체, 수가 모두 다르며
측정명만 있을 뿐, 데이터셋에서는 주소와 위치 정보를 제공하지 않음
서로 연관성을 찾기 어려움
이에 제3의 데이터를 이용해 주소 및 위치 정보를 붙이고, 이들을 거리 기반으로 묶어 연관시켜주는 절차 필요

(1) X(설명 변수) 구축

설명 변수에 해당하는 수질 데이터의 낙동강 측정소 261곳의 위치 정보를 다른 데이터셋에서 수집해 좌표 정보 결합
(물환경 수질측정망 정보 공간데이터셋 shp 파일, https://www.data.go.kr/data/15111317/fileData.do)
전국 실폭하천 지도에 1200여 측정소 위치 표시
목표 변수(Y, 녹조 데이터셋)에 존재하는 측정소 이름만 추림(낙동강 수계): 264개
도심 하천 구간을 포함한 전국 1254개 수질 측정소(2023년 기준) 가운데 낙동강수계에 260곳 이상 몰려 있음(위 그림). 이들 위치 정보를 이용해 수질측정망 위치를 확정
주의: 금천, 사천천, 한천 등 동명 하천 4곳 정보 정제 뒤 시행
이런 데이터를 보면 '어쩌라고' 이런 생각이..
수동 정제함: 총 261개로 정리

(2) Y(독립 변수) 구축

타깃에 해당하는 녹조 측정 장소 36곳은 측정소 주소 정보를 홈페이지에서 구한 뒤,
이를 kakao map api를 이용해 위도, 경도 좌표로 변환(지오코딩)
지오코딩 성공
X 및 Y 데이터 바탕으로 둘 동시에 지도에 표시해보면

Part 3. X - Y 매칭

Y에 해당하는 녹조 측정이 이뤄진 36개 측정소, 1만 2000여 데이터와 관련이 있을 것으로 추정되는 X 측정소는 소수임.
관련 있는 X(수질 측정소 데이터)를 추려야 함: 거리 기반
방법 1: 녹조 측정 지점 반경 x km 지점을 정해 공간 연산 통해 관련 있는 수질 측정소를 추린 뒤 거리 기반 매칭
방법 2: 그냥 거리 기반으로 매칭하되, 일정 거리 이상은 제거
아래는 녹지 측정소 반경 10km, 5km 이내를 표시한 것
10km일 경우: 204개 측정소 2만 3000여 데이터 선정
5km 일 경우: 93개 측정소 1만 5000여 데이터 선정
어떻게 매칭시킬까
방법 1: 각 수질 측정소(X)를 가장 가까운 녹조 측정소(Y)와 연결
—> 가장 간단하지만, 현실을 잘 반영할지 의문
방법 2: 각 녹조 측정소(Y) 주변의 정해진 반경(5km 또는 10km) 속 수질 측정소(X)를 모두 Y에 매칭
—> 주변 일정한 거리의 수질 정보를 다 반영할 수 있어 유리.
단, 방법은 고민해야
<<현재까지의 파일>>
매칭 결과: 근데 이렇게 하면 너무 많은 데이터가 나올 듯… 추려야 하나?
X_station Y_station distance 1 왜관 낙동강(해평) 4384.7151 2 구미 낙동강(해평) 7244.5264 3 칠곡 낙동강(해평) 1383.9565 4 계성천 낙동강(칠서) 5584.1067 5 남지 낙동강(칠서) 5918.8263 6 광려천4 낙동강(칠서) 2278.2567 7 광려천3 낙동강(칠서) 5953.9450 8 북면 낙동강(칠서) 9909.8165 9 임해진 낙동강(칠서) 6587.0792 10 함안 낙동강(칠서) 1994.1537 11 동창천1 운문호(댐앞) 6032.2489 12 부일천 운문호(댐앞) 2297.5445 13 동창천1 운문호(취수탑2) 2923.1594 14 부일천 운문호(취수탑2) 4023.0454 15 죽장천 영천호(취수탑) 3569.8502 16 금호강1 영천호(취수탑) 7893.7474 17 자호천 영천호(취수탑) 4970.1683 18 사천천(가화천) 진양호(내동) 9636.7895 19 남강1 진양호(내동) 4493.3213 20 기계천 안계호(취수탑) 3544.4620 21 형산강4 안계호(취수탑) 5193.1437 22 형산강5 안계호(취수탑) 9912.1072 23 칠성천 안계호(취수탑) 9882.3792 24 형산강6 안계호(취수탑) 9544.3270 25 금호강5 공산지(중앙부) 6832.6274 26 금호강6 공산지(중앙부) 6750.7370 27 팔거천1 공산지(중앙부) 8227.7993 28 금호강5 공산지(취수탑) 6832.6274 29 금호강6 공산지(취수탑) 6750.7370 30 팔거천1 공산지(취수탑) 8227.7993 31 형산강4 진전지(상류) 7641.9041 32 형산강5 진전지(상류) 2466.6899 33 칠성천 진전지(상류) 2099.7901 34 형산강6 진전지(상류) 1993.3080 35 냉천 진전지(상류) 4092.0456 36 형산강4 진전지(하류) 7641.9041 37 형산강5 진전지(하류) 2466.6899 38 칠성천 진전지(하류) 2099.7901 39 형산강6 진전지(하류) 1993.3080 40 냉천 진전지(하류) 4092.0456 41 작동교 사연호(취수탑) 6937.8519 42 하잠교 사연호(취수탑) 7247.6983 43 대곡천2 사연호(취수탑) 4904.7839 44 대곡천1 사연호(취수탑) 9788.2693 45 구영 사연호(취수탑) 4400.7306 46 망성 사연호(취수탑) 2530.1930 47 지헌 사연호(취수탑) 9568.7507 48 대암 사연호(취수탑) 3502.4223 49 반송 사연호(취수탑) 6046.3398 50 삼호 사연호(취수탑) 6924.8789 51 태화 사연호(취수탑) 9581.9264 52 신화 사연호(취수탑) 7584.9221 53 보은천 사연호(반연리) 9330.9434 54 작동교 사연호(반연리) 6675.5206 55 하잠교 사연호(반연리) 6699.2737 56 대곡천2 사연호(반연리) 4033.3111 57 대곡천1 사연호(반연리) 9374.1904 58 구영 사연호(반연리) 5929.8952 59 망성 사연호(반연리) 4026.4527 60 지헌 사연호(반연리) 7993.8597 61 대암 사연호(반연리) 3297.7351 62 반송 사연호(반연리) 4964.1351 63 삼호 사연호(반연리) 8462.7388 64 신화 사연호(반연리) 6449.0003 65 남강3 진양호(판문) 8366.9210 66 남강1 진양호(판문) 2855.0794 67 남강2 진양호(판문) 7182.5990 68 화원나루 낙동강(강정·고령) 4342.7659 69 용암 낙동강(강정·고령) 6913.7991 70 다사 낙동강(강정·고령) 971.2662 71 달성 낙동강(강정·고령) 870.3569 72 하빈천 낙동강(강정·고령) 4524.3799 73 백천2 낙동강(강정·고령) 7966.7647 74 성주 낙동강(강정·고령) 7280.3273 75 금호강8 낙동강(강정·고령) 933.9527 76 금호강7 낙동강(강정·고령) 9255.4695 77 양산천3 낙동강(물금·매리) 9142.6852 78 금곡 낙동강(물금·매리) 9726.1338 79 양산천2 낙동강(물금·매리) 9522.4986 80 물금 낙동강(물금·매리) 4859.0376 81 병성천 상주보(도남) 2156.7596 82 영강2 상주보(도남) 8997.4434 83 영강3 상주보(도남) 9835.0394 84 이안천 상주보(도남) 9729.4609 85 위천5 상주보(도남) 8165.2211 86 도남 상주보(도남) 1602.0051 87 상주1 상주보(도남) 5401.2399 88 영순 상주보(도남) 9821.6805 89 상주2 상주보(도남) 6711.6791 90 말지천 상주보(도남) 7148.5729 91 병성천 낙단보(낙단) 9900.6842 92 위천5 낙단보(낙단) 3875.4852 93 위천4 낙단보(낙단) 8008.6399 94 낙단 낙단보(낙단) 515.1861 95 상주3 낙단보(낙단) 1040.7046 96 도남 낙단보(낙단) 9127.1488 97 상주2 낙단보(낙단) 4599.3465 98 말지천 낙단보(낙단) 7076.6262 99 감천4 구미보(선산) 3130.4480 100 감천3 구미보(선산) 2232.8781 101 강정 구미보(선산) 6512.8837 102 산곡 구미보(선산) 507.6795 103 선산 구미보(선산) 3227.8035 104 왜관 칠곡보(칠곡) 4384.7151 105 구미 칠곡보(칠곡) 7244.5264 106 칠곡 칠곡보(칠곡) 1383.9565 107 화원나루 강정고령보(다사) 4342.7659 108 용암 강정고령보(다사) 6913.7991 109 다사 강정고령보(다사) 971.2662 110 달성 강정고령보(다사) 870.3569 111 하빈천 강정고령보(다사) 4524.3799 112 백천2 강정고령보(다사) 7966.7647 113 성주 강정고령보(다사) 7280.3273 114 금호강8 강정고령보(다사) 933.9527 115 금호강7 강정고령보(다사) 9255.4695 116 화원나루 달성보(논공) 8228.6506 117 고령 달성보(논공) 3722.0625 118 논공 달성보(논공) 1529.6040 119 차천 달성보(논공) 5711.2884 120 현풍 달성보(논공) 4987.4547 121 창녕 합천창녕보(덕곡) 1149.0893 122 덕곡 합천창녕보(덕곡) 1716.0470 123 대암리 합천창녕보(덕곡) 2041.0828 124 황강5 합천창녕보(덕곡) 5693.3845 125 황강6 합천창녕보(덕곡) 3257.4492 126 회천2 합천창녕보(덕곡) 8573.0313 127 회천3 합천창녕보(덕곡) 1689.4414 128 토평천2 합천창녕보(덕곡) 9792.7734 129 합천 합천창녕보(덕곡) 9227.8056 130 계성천 창녕함안보(함안) 5584.1067 131 남지 창녕함안보(함안) 5918.8263 132 광려천4 창녕함안보(함안) 2278.2567 133 광려천3 창녕함안보(함안) 5953.9450 134 북면 창녕함안보(함안) 9909.8165 135 임해진 창녕함안보(함안) 6587.0792 136 함안 창녕함안보(함안) 1994.1537 137 화원나루 낙동강_시범(고령 지점 표층) 5948.6434 138 용암 낙동강_시범(고령 지점 표층) 3049.1896 139 다사 낙동강_시범(고령 지점 표층) 3242.9403 140 달성 낙동강_시범(고령 지점 표층) 3913.0596 141 하빈천 낙동강_시범(고령 지점 표층) 1179.6962 142 백천2 낙동강_시범(고령 지점 표층) 4289.5010 143 성주 낙동강_시범(고령 지점 표층) 3613.5662 144 금호강8 낙동강_시범(고령 지점 표층) 4659.1071 145 화원나루 낙동강_시범(고령 지점 혼합) 5948.6434 146 용암 낙동강_시범(고령 지점 혼합) 3049.1896 147 다사 낙동강_시범(고령 지점 혼합) 3242.9403 148 달성 낙동강_시범(고령 지점 혼합) 3913.0596 149 하빈천 낙동강_시범(고령 지점 혼합) 1179.6962 150 백천2 낙동강_시범(고령 지점 혼합) 4289.5010 151 성주 낙동강_시범(고령 지점 혼합) 3613.5662 152 금호강8 낙동강_시범(고령 지점 혼합) 4659.1071 153 양산천3 낙동강_시범(매리 지점 표층) 5508.4699 154 금곡 낙동강_시범(매리 지점 표층) 5940.3160 155 서낙동강1 낙동강_시범(매리 지점 표층) 8961.3041 156 양산천2 낙동강_시범(매리 지점 표층) 8352.3129 157 물금 낙동강_시범(매리 지점 표층) 1735.4020 158 양산천3 낙동강_시범(매리 지점 혼합) 5508.4699 159 금곡 낙동강_시범(매리 지점 혼합) 5940.3160 160 서낙동강1 낙동강_시범(매리 지점 혼합) 8961.3041 161 양산천2 낙동강_시범(매리 지점 혼합) 8352.3129 162 물금 낙동강_시범(매리 지점 혼합) 1735.4020 163 고령 낙동강_시범(레포츠밸리) 6920.4160 164 논공 낙동강_시범(레포츠밸리) 6816.0377 165 창녕 낙동강_시범(레포츠밸리) 8462.1694 166 대암리 낙동강_시범(레포츠밸리) 8449.5022 167 차천 낙동강_시범(레포츠밸리) 4481.8874 168 현풍 낙동강_시범(레포츠밸리) 5168.2026 169 회천2 낙동강_시범(레포츠밸리) 3030.7755 170 회천3 낙동강_시범(레포츠밸리) 8094.6032 171 구미 낙동강_시범(구미낙동강레포츠체험센터) 4423.2474 172 한천2 낙동강_시범(구미낙동강레포츠체험센터) 2015.3628 173 한천1 낙동강_시범(구미낙동강레포츠체험센터) 7826.4038 174 하남 낙동강_시범(본포수변생태공원) 5007.9004 175 주천강 낙동강_시범(본포수변생태공원) 9458.0901 176 북면 낙동강_시범(본포수변생태공원) 822.7867 177 임해진 낙동강_시범(본포수변생태공원) 5236.8673 178 주항천 낙동강_시범(본포수변생태공원) 9518.3995 179 청도천1 낙동강_시범(본포수변생태공원) 8139.3987 180 청도천2 낙동강_시범(본포수변생태공원) 6347.7722 181 함안 낙동강_시범(본포수변생태공원) 9015.5875 182 양산천3 낙동강_시범(화명수상레포츠타운) 8226.2280 183 금곡 낙동강_시범(화명수상레포츠타운) 7126.5616 184 구포 낙동강_시범(화명수상레포츠타운) 2253.1533 185 서낙동강1 낙동강_시범(화명수상레포츠타운) 6666.6538
이렇게 하면 다대일 매칭을 해야 하는데… 꽤 복잡할 듯.
유클리드 거리 가장 가까운 곳 매칭해서 1:1로 해보면 어떨지
Y_station min_distance_X_station min_distance 1 강정고령보(다사) 달성 870.3569 2 공산지(중앙부) 금호강6 6750.7370 3 공산지(취수탑) 금호강6 6750.7370 4 구미보(선산) 산곡 507.6795 5 낙단보(낙단) 낙단 515.1861 6 낙동강(강정·고령) 달성 870.3569 7 낙동강(물금·매리) 물금 4859.0376 8 낙동강(칠서) 함안 1994.1537 9 낙동강(해평) 칠곡 1383.9565 10 낙동강_시범(고령 지점 표층) 하빈천 1179.6962 11 낙동강_시범(고령 지점 혼합) 하빈천 1179.6962 12 낙동강_시범(구미낙동강레포츠체험센터) 한천2 2015.3628 13 낙동강_시범(레포츠밸리) 회천2 3030.7755 14 낙동강_시범(매리 지점 표층) 물금 1735.4020 15 낙동강_시범(매리 지점 혼합) 물금 1735.4020 16 낙동강_시범(본포수변생태공원) 북면 822.7867 17 낙동강_시범(화명수상레포츠타운) 구포 2253.1533 18 달성보(논공) 논공 1529.6040 19 사연호(반연리) 대암 3297.7351 20 사연호(취수탑) 망성 2530.1930 21 상주보(도남) 도남 1602.0051 22 안계호(취수탑) 기계천 3544.4620 23 영천호(취수탑) 죽장천 3569.8502 24 운문호(댐앞) 부일천 2297.5445 25 운문호(취수탑2) 동창천1 2923.1594 26 진양호(내동) 남강1 4493.3213 27 진양호(판문) 남강1 2855.0794 28 진전지(상류) 형산강6 1993.3080 29 진전지(하류) 형산강6 1993.3080 30 창녕함안보(함안) 함안 1994.1537 31 칠곡보(칠곡) 칠곡 1383.9565 32 합천창녕보(덕곡) 창녕 1149.0893
샘플로, 창녕함안보를 대상으로 X, Y 결합해봄(full_join / outer_join)
동일하게 일주일 간격으로 채수한 데이터이고
날짜로 거의 비슷
위 내용 바탕으로 피처를 선택한 뒤, 반경 10km 구간 내 모든 수질 측정소 데이터를 이용해 선택 피처 평균값으로 녹조 발생을 예측하는 것도 가능할 듯
참고로 전에 Jena로 만들었던 NN CNN 초기 코드에 위 파일 넣어본 사례

TODO

수질 + 녹조 merge
위치 정보
시간 정보
녹조 예측에 중요한 feature 확인
4종류의 녹조 세포? 확인
녹조에 영향을 미치는 요소 확인
수온
오염수
농약
등등..
보/댐 정보 확인
유수량이 많으면 어쩌구… 녹조 발생률 높다고 한 것같음
→ 도메인 조사를 바탕으로 이러한 요소로 EDA 수행
코드는 다 있으니까… merge랑 도메인 조사만 어떻게 하면 금방 끝날듯…
그리고 녹조 경보 기준이 있는 거 같은데 그거 확인
그렇다면 근접한 위치를 어떻게 묶을까?
위치 정보
우리의 관심 지역은 낙동강
1.
때문에 낙동강을 기준으로 녹조 정보를 수집한 위치 목록을 추출
2.
위 정보를 기반으로, 특정 거리 이내에 수질측정망를 수집한 위치가 있는지 list up
3.
근접한 관측소가 추출
시간 정보
수집된 시점이 동일하지 않음
수집된 시기를 확인해서 적당한 범위 내에 up sampling 수행
녹조 측정 위치를 기준으로 공통된 위치를 merge
수질측정망 측정 위치와, 녹조정보 측정 위치가 겹치는 것 뿐만아니라
강의 상/하류를 정보를 포함하여 더 공통된 위치를 추출할 수도 있겠지만
그렇게 하기에는 더 심화된 도메인 지식 공부가 필요함
현재는 위 상황까지 고려한 작업을 수행하기에는 시간이 부족한 상황
단순히 특정 거리 이내에 겹치는 지점을 기준으로 merge 하는 것이 최선이라고 생각
환경부 국립환경과학원에서 제공하는 물 환경 대시보드 GUI
수질측정망(261)과 녹조정보(26)의 수집 위치가 각각 다름
대부분은 수질측정망과 녹조정보가 수집된 위치가 근접해 있지만, 일부 아닌 곳도 존재
ㅇㅋ 그러면 이제 지역을 묶어야함
위치 정보를 어떻게 가져오지?
수질측정망
일단 수질측정망 위치정보를 제공하는 shp파일이 있음
근데 수질측정망 데이터셋에 위도경도 가 있긴함
shp파일은 필요없을듯?
녹조정보
26에 대한 녹조정보 수집 위치 추출해야할듯
낙동강(해평)
경상북도 칠곡군 석적읍 중지리
낙동강물환경연구소
낙동강(칠서)
경상남도 함안군 칠북면 이령리
낙동강물환경연구소
운문호(댐앞)
경상북도 청도군 운문면 대천리
대구지방환경청
운문호(취수탑2)
경상북도 청도군 운문면 서지리
대구지방환경청
영천호(취수탑)
경상북도 영천시 자양면 삼귀리
대구지방환경청
진양호(내동)
경상남도 사천시 곤명면 연평리
낙동강유역환경청
안계호(취수탑)
경상북도 경주시 강동면 안계리
대구지방환경청
공산지(중앙부)
대구광역시 동구 지묘동
대구시 상수도사업본부
공산지(취수탑)
대구광역시 동구 지묘동
대구시 상수도사업본부
진전지(상류)
경상북도 포항시 남구 연일읍 오천리
경상북도 보건환경연구원
진전지(하류)
경상북도 포항시 남구 연일읍 오천리
경상북도 보건환경연구원
사연호(취수탑)
울산광역시 울주군 범서읍 사연리
낙동강유역환경청
사연호(반연리)
울산광역시 울주군 언양읍 반연리
낙동강유역환경청
덕동호(댐앞)
경상북도 경주시 덕동
낙동강물환경연구소
낙동강(물금·매리)
경상남도 김해시 상동면 감노리
낙동강물환경연구소
진양호(판문)
경상남도 진주시 판문동
낙동강유역환경청
낙동강(강정·고령)
대구광역시 달성군 다사읍 죽곡리
낙동강물환경연구소
근데 26개가 아님… 뭐야 진짜

📍 도메인 조사

3. EDA & Preprocess 주요 인사이트

file_path = 'Datasets/XY_cnhaman_1vs1_3.csv' drop_cols = ['연도', '월', '조사회차', '수계.명',"longitude.x","latitude.x","분류","지점명","채수위치","수온","addr","location","no","latitude.y","longitude.y"] time_col = '채수.일자' zero_cols = ["Microcystis", "Anabaena", "Oscillatoria", "Aphanizomenon"] df = load_data(file_path, drop_cols, time_col, zero_cols) df.info()
target = '유해남조류.세포수..cells...' numeric_cols = df.select_dtypes(include=['number']).columns fig, axes = plt.subplots(nrows=len(numeric_cols), ncols=1, figsize=(12, 3 * len(df.columns)), sharex=True) for i, col in enumerate(numeric_cols): axes[i].plot(df.index, df[col], label=col, color='tab:blue') axes[i].set_ylabel(col) axes[i].legend(loc='upper right') axes[i].grid(True) plt.xlabel("Date") plt.tight_layout() plt.show()
numeric_columns = df.select_dtypes(include=[np.number]).columns.tolist() corr_matrix = df[numeric_columns].corr() print("Correlation matrix : ") print(corr_matrix) plt.figure(figsize=(18, 18)) sns.heatmap(corr_matrix, annot=True, fmt=".2f", cmap='coolwarm', square=True, cbar_kws={"shrink": .8}) plt.title('Correlation Matrix')
🛠️Feature Engeneering
def month_to_season(month): if month in [3, 4, 5]: return 'Spring' elif month in [6, 7, 8]: return 'Summer' elif month in [9, 10, 11]: return 'Autumn' else: # 12, 1, 2 return 'Winter' df['year'] = df.index.year df['month'] = df.index.month df['season'] = df['month'].apply(month_to_season)

연도별 Chl-a 분포

plt.figure(figsize=(6,4)) years= range(2016, 2025) data = [df[df['year']==y][target] for y in years] plt.boxplot(data, labels=years) plt.title('연도별 유해 남조류 수 분포') plt.ylabel('µg/L') plt.tight_layout() plt.show()

월별 Chl-a 분포

plt.figure(figsize=(6,4)) months = range(1, 13) data = [df[df['month']==m][target] for m in months] plt.boxplot(data, labels=months) plt.title('월별 유해 남조류 수 분포') plt.ylabel('µg/L') plt.tight_layout() plt.show()

계절별 Chl-a 분포

plt.figure(figsize=(6,4)) seasons = ['Spring','Summer','Autumn','Winter'] data = [df[df['season']==s][target] for s in seasons] plt.boxplot(data, labels=seasons) plt.title('계절별 유해 남조류 수 분포') plt.ylabel('µg/L') plt.tight_layout() plt.show()

CCF**(Cross-Correlation Fuction)**

시계열 A와 시계열 B 간에, 서로 시간적으로 얼마나 앞서거나 뒤처졌을 때 가장 높은 상관관계를 보이는지를 분석하는 도구.
from statsmodels.tsa.stattools import ccf def decompose_and_plot(ts: pd.Series, title: str, period: int = 365): """ 시계열 분해(추세, 계절성, 잔차) 및 플롯 - ts: DatetimeIndex를 갖는 pandas Series - title: 플롯 제목 - period: 계절 주기 (일 단위) """ decomp = seasonal_decompose(ts, model='additive', period=period) fig, axes = plt.subplots(3, 1, figsize=(10, 8), sharex=True) decomp.trend.plot(ax=axes[0], title=f'{title} — 추세') decomp.seasonal.plot(ax=axes[1], title=f'{title} — 계절성') decomp.resid.plot(ax=axes[2], title=f'{title} — 잔차') plt.tight_layout() plt.show() def plot_ccf(series_x: pd.Series, series_y: pd.Series, max_lag: int = 60, title: str = 'CCF'): """ 두 시계열 간 교차상관함수(CCF) 플롯 - series_x: 선행(화학 변수) - series_y: 결과(Bloom) """ x = series_x.values y = series_y.values c = ccf(x, y)[:max_lag] plt.figure(figsize=(6, 3)) plt.stem(range(len(c)), c) plt.title(title) plt.xlabel('Lag (days)') plt.ylabel('Cross-correlation') plt.tight_layout() plt.show() # 3) 시계열 분해 및 플롯 decompose_and_plot(df[target], '낙동강 유해남조류 수', period=4*12)
# 4) 주요 화학 변수 vs Bloom CCF 분석 chem_vars = ['클로로필.a.Chlorophyll.a.', '수온...'] for var in numeric_cols: plot_ccf(df[var], df[target], max_lag=60, title=f'CCF: {var} → 유해남조류 수')
def crate_dataset(df, train_ratio, target, features=None): n = len(df) train_size = int(n*train_ratio) if features: X = df[features] y = df[target] X_train = X[:train_size] X_test = X[train_size:] y_train = y[:train_size] y_test = y[train_size:] return X_train, y_train, X_test, y_test else: data = df[target] train = data[:train_size] test = data[train_size:] return train, test
def check_stationarity(data): adf = adfuller(data) print(f"ADF Statistic: {adf[0]}") print(f"p-value: {adf[1]}") print(f"Critical Values: {adf[4]}") if adf[1] < 0.01: print("정상성") else: print("비정상성") fig, ax = plt.subplots(1, 2 ,figsize = (12, 5)) plot_acf(data.diff().dropna(), ax = ax[0]) plot_pacf(data.diff().dropna(), ax = ax[1]) plt.show()
-
paths = 'Datasets/XY_cnhaman_1vs1_3.csv' df = pd.read_csv(paths, index_col=0) df.head()
연도 월 조사회차 채수.일자 수계.명 중권역.명 측정소.명 수심 음이온계면활성제.ABS. 유량 ... Microcystin.RR Microcystin.LA Microcystin.YR Microcystin.LF Microcystin.LY location addr no latitude.y longitude.y 1 2016.0 1.0 1회차 2016-01-04 낙동강 낙동밀양 함안 NaN NaN NaN ... NaN NaN NaN NaN NaN 창녕함안보(함안) 경상남도 함안군 칠북면 이령리 36.0 35.366363 128.536973 2 2016.0 1.0 2회차 2016-01-11 낙동강 낙동밀양 함안 NaN NaN NaN ... NaN NaN NaN NaN NaN 창녕함안보(함안) 경상남도 함안군 칠북면 이령리 36.0 35.366363 128.536973 3 2016.0 1.0 3회차 2016-01-18 낙동강 낙동밀양 함안 NaN NaN NaN ... NaN NaN NaN NaN NaN 창녕함안보(함안) 경상남도 함안군 칠북면 이령리 36.0 35.366363 128.536973 4 2016.0 1.0 4회차 2016-01-26 낙동강 낙동밀양 함안 NaN NaN NaN ... NaN NaN NaN NaN NaN 창녕함안보(함안) 경상남도 함안군 칠북면 이령리 36.0 35.366363 128.536973 5 2016.0 2.0 1회차 2016-02-01 낙동강 낙동밀양 함안 NaN NaN NaN ... NaN NaN NaN NaN NaN 창녕함안보(함안) 경상남도 함안군 칠북면 이령리 36.0 35.366363 128.536973 5 rows × 93 columns
무의미한 열 제거
drop_cols = ['연도', '월', '조사회차', '수계.명',"longitude.x","latitude.x","분류","지점명","채수위치","수온","addr","location","no","latitude.y","longitude.y"] df.drop(drop_cols, axis=1, inplace=True) df['채수.일자'] = pd.to_datetime(df['채수.일자']) df = df.set_index('채수.일자') df.head()
중권역.명 측정소.명 수심 음이온계면활성제.ABS. 유량 안티몬.Sb. 비소.As. 바륨.Ba. 벤젠 생물화학적산소요구량.BOD. ... Oscillatoria Aphanizomenon 지오스민 X2MIB Microcystin.LR Microcystin.RR Microcystin.LA Microcystin.YR Microcystin.LF Microcystin.LY 채수.일자 2016-01-04 낙동밀양 함안 NaN NaN NaN NaN NaN NaN NaN 3.6 ... NaN NaN 16.0 NaN NaN NaN NaN NaN NaN NaN 2016-01-11 낙동밀양 함안 NaN NaN NaN NaN NaN NaN NaN 1.8 ... NaN NaN 14.0 NaN NaN NaN NaN NaN NaN NaN 2016-01-18 낙동밀양 함안 NaN NaN NaN NaN NaN NaN NaN 1.3 ... NaN NaN 16.0 NaN NaN NaN NaN NaN NaN NaN 2016-01-26 낙동밀양 함안 NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2016-02-01 낙동밀양 함안 NaN NaN NaN NaN NaN NaN NaN 1.1 ... NaN NaN 13.0 NaN NaN NaN NaN NaN NaN NaN 5 rows × 77 columns

측정값 0 처리

일부 측정값이 0인데, Nan으로 입력된 경우 존재
이에 해당하는 열에 대하여, Nan값을 0으로 채우는 작업 수행
zero_cols = ["Microcystis", "Anabaena", "Oscillatoria", "Aphanizomenon"] df[zero_cols] = df[zero_cols].fillna(0)

결측치 제거

결측치 개수를 기반으로 threshold를 설정
임계치 기반으로 결측치가 많은 열을 제거
제거되지 않은 열은 보간법 등을 통해 결측치 처리
df.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 494 entries, 2016-01-04 to 2025-05-07 Data columns (total 77 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 중권역.명 470 non-null object 1 측정소.명 470 non-null object 2 수심 0 non-null float64 3 음이온계면활성제.ABS. 0 non-null float64 4 유량 0 non-null float64 5 안티몬.Sb. 29 non-null float64 6 비소.As. 0 non-null float64 7 바륨.Ba. 0 non-null float64 8 벤젠 0 non-null float64 9 생물화학적산소요구량.BOD. 442 non-null float64 10 사염화탄소 0 non-null float64 11 카드뮴.Cd. 0 non-null float64 12 클로로포름 0 non-null float64 13 염소이온.Cl.. 0 non-null float64 14 클로로필.a.Chlorophyll.a. 442 non-null float64 15 시안.CN. 0 non-null float64 16 화학적산소요구량.COD. 442 non-null float64 17 색도 0 non-null float64 18 크롬.Cr. 0 non-null float64 19 X6가크롬.Cr6.. 0 non-null float64 20 구리.Cu. 0 non-null float64 21 X1.2..다이클로로에탄 0 non-null float64 22 다이클로로메탄 0 non-null float64 23 다이에틸헥실프탈레이트.DEHP. 0 non-null float64 24 X1.4.다이옥세인 0 non-null float64 25 용존산소.DO. 442 non-null float64 26 용존총질소.DTN. 442 non-null float64 27 용존총인.DTP. 442 non-null float64 28 전기전도도.EC. 442 non-null float64 29 분원성대장균군 442 non-null float64 30 용해성.철.Fe. 0 non-null float64 31 불소.F. 0 non-null float64 32 헥사클로로벤젠 0 non-null float64 33 포름알데히드 0 non-null float64 34 수은.Hg. 0 non-null float64 35 용해성.망간.Mn. 0 non-null float64 36 암모니아성.질소.NH3.N. 441 non-null float64 37 노말헥산추출물질 0 non-null float64 38 니켈.Ni. 0 non-null float64 39 질산성질소.NO3.N. 442 non-null float64 40 유기인 0 non-null float64 41 납.Pb. 0 non-null float64 42 폴리클로리네이티드비페닐.PCB. 0 non-null float64 43 테트라클로로에틸렌.PCE. 0 non-null float64 44 수소이온농도.pH. 442 non-null float64 45 페놀류.phenols. 0 non-null float64 46 인산염.인.PO4.P. 243 non-null float64 47 셀레늄.Se. 0 non-null float64 48 부유물질.SS. 442 non-null float64 49 트리클로로에틸렌.TCE. 0 non-null float64 50 총대장균군 442 non-null float64 51 총질소.T.N. 442 non-null float64 52 총유기탄소.TOC. 442 non-null float64 53 총인.T.P. 442 non-null float64 54 투명도.x 0 non-null float64 55 아연.Zn. 0 non-null float64 56 TYPE 470 non-null object 57 PTNO 470 non-null object 58 수온... 457 non-null float64 59 pH 457 non-null float64 60 DO...L. 457 non-null float64 61 투명도.y 235 non-null float64 62 탁도 235 non-null float64 63 Chl.a...... 457 non-null float64 64 유해남조류.세포수..cells... 457 non-null float64 65 Microcystis 494 non-null float64 66 Anabaena 494 non-null float64 67 Oscillatoria 494 non-null float64 68 Aphanizomenon 494 non-null float64 69 지오스민 208 non-null float64 70 X2MIB 99 non-null float64 71 Microcystin.LR 0 non-null float64 72 Microcystin.RR 0 non-null float64 73 Microcystin.LA 0 non-null float64 74 Microcystin.YR 0 non-null float64 75 Microcystin.LF 0 non-null float64 76 Microcystin.LY 0 non-null float64 dtypes: float64(73), object(4) memory usage: 301.0+ KB
threshold : 50
측정 값 이다 보니까 -> 검출되지 않았다.
df = df.dropna(axis=0, thresh=20) df.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 442 entries, 2016-01-04 to 2025-02-24 Data columns (total 77 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 중권역.명 442 non-null object 1 측정소.명 442 non-null object 2 수심 0 non-null float64 3 음이온계면활성제.ABS. 0 non-null float64 4 유량 0 non-null float64 5 안티몬.Sb. 29 non-null float64 6 비소.As. 0 non-null float64 7 바륨.Ba. 0 non-null float64 8 벤젠 0 non-null float64 9 생물화학적산소요구량.BOD. 442 non-null float64 10 사염화탄소 0 non-null float64 11 카드뮴.Cd. 0 non-null float64 12 클로로포름 0 non-null float64 13 염소이온.Cl.. 0 non-null float64 14 클로로필.a.Chlorophyll.a. 442 non-null float64 15 시안.CN. 0 non-null float64 16 화학적산소요구량.COD. 442 non-null float64 17 색도 0 non-null float64 18 크롬.Cr. 0 non-null float64 19 X6가크롬.Cr6.. 0 non-null float64 20 구리.Cu. 0 non-null float64 21 X1.2..다이클로로에탄 0 non-null float64 22 다이클로로메탄 0 non-null float64 23 다이에틸헥실프탈레이트.DEHP. 0 non-null float64 24 X1.4.다이옥세인 0 non-null float64 25 용존산소.DO. 442 non-null float64 26 용존총질소.DTN. 442 non-null float64 27 용존총인.DTP. 442 non-null float64 28 전기전도도.EC. 442 non-null float64 29 분원성대장균군 442 non-null float64 30 용해성.철.Fe. 0 non-null float64 31 불소.F. 0 non-null float64 32 헥사클로로벤젠 0 non-null float64 33 포름알데히드 0 non-null float64 34 수은.Hg. 0 non-null float64 35 용해성.망간.Mn. 0 non-null float64 36 암모니아성.질소.NH3.N. 441 non-null float64 37 노말헥산추출물질 0 non-null float64 38 니켈.Ni. 0 non-null float64 39 질산성질소.NO3.N. 442 non-null float64 40 유기인 0 non-null float64 41 납.Pb. 0 non-null float64 42 폴리클로리네이티드비페닐.PCB. 0 non-null float64 43 테트라클로로에틸렌.PCE. 0 non-null float64 44 수소이온농도.pH. 442 non-null float64 45 페놀류.phenols. 0 non-null float64 46 인산염.인.PO4.P. 243 non-null float64 47 셀레늄.Se. 0 non-null float64 48 부유물질.SS. 442 non-null float64 49 트리클로로에틸렌.TCE. 0 non-null float64 50 총대장균군 442 non-null float64 51 총질소.T.N. 442 non-null float64 52 총유기탄소.TOC. 442 non-null float64 53 총인.T.P. 442 non-null float64 54 투명도.x 0 non-null float64 55 아연.Zn. 0 non-null float64 56 TYPE 442 non-null object 57 PTNO 442 non-null object 58 수온... 442 non-null float64 59 pH 442 non-null float64 60 DO...L. 442 non-null float64 61 투명도.y 222 non-null float64 62 탁도 222 non-null float64 63 Chl.a...... 442 non-null float64 64 유해남조류.세포수..cells... 442 non-null float64 65 Microcystis 442 non-null float64 66 Anabaena 442 non-null float64 67 Oscillatoria 442 non-null float64 68 Aphanizomenon 442 non-null float64 69 지오스민 206 non-null float64 70 X2MIB 99 non-null float64 71 Microcystin.LR 0 non-null float64 72 Microcystin.RR 0 non-null float64 73 Microcystin.LA 0 non-null float64 74 Microcystin.YR 0 non-null float64 75 Microcystin.LF 0 non-null float64 76 Microcystin.LY 0 non-null float64 dtypes: float64(73), object(4) memory usage: 269.3+ KB
df = df.dropna(axis=1, how='any') df.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 442 entries, 2016-01-04 to 2025-02-24 Data columns (total 28 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 중권역.명 442 non-null object 1 측정소.명 442 non-null object 2 생물화학적산소요구량.BOD. 442 non-null float64 3 클로로필.a.Chlorophyll.a. 442 non-null float64 4 화학적산소요구량.COD. 442 non-null float64 5 용존산소.DO. 442 non-null float64 6 용존총질소.DTN. 442 non-null float64 7 용존총인.DTP. 442 non-null float64 8 전기전도도.EC. 442 non-null float64 9 분원성대장균군 442 non-null float64 10 질산성질소.NO3.N. 442 non-null float64 11 수소이온농도.pH. 442 non-null float64 12 부유물질.SS. 442 non-null float64 13 총대장균군 442 non-null float64 14 총질소.T.N. 442 non-null float64 15 총유기탄소.TOC. 442 non-null float64 16 총인.T.P. 442 non-null float64 17 TYPE 442 non-null object 18 PTNO 442 non-null object 19 수온... 442 non-null float64 20 pH 442 non-null float64 21 DO...L. 442 non-null float64 22 Chl.a...... 442 non-null float64 23 유해남조류.세포수..cells... 442 non-null float64 24 Microcystis 442 non-null float64 25 Anabaena 442 non-null float64 26 Oscillatoria 442 non-null float64 27 Aphanizomenon 442 non-null float64 dtypes: float64(24), object(4) memory usage: 100.1+ KB
df = df.interpolate(method='time') df.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 442 entries, 2016-01-04 to 2025-02-24 Data columns (total 28 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 중권역.명 442 non-null object 1 측정소.명 442 non-null object 2 생물화학적산소요구량.BOD. 442 non-null float64 3 클로로필.a.Chlorophyll.a. 442 non-null float64 4 화학적산소요구량.COD. 442 non-null float64 5 용존산소.DO. 442 non-null float64 6 용존총질소.DTN. 442 non-null float64 7 용존총인.DTP. 442 non-null float64 8 전기전도도.EC. 442 non-null float64 9 분원성대장균군 442 non-null float64 10 질산성질소.NO3.N. 442 non-null float64 11 수소이온농도.pH. 442 non-null float64 12 부유물질.SS. 442 non-null float64 13 총대장균군 442 non-null float64 14 총질소.T.N. 442 non-null float64 15 총유기탄소.TOC. 442 non-null float64 16 총인.T.P. 442 non-null float64 17 TYPE 442 non-null object 18 PTNO 442 non-null object 19 수온... 442 non-null float64 20 pH 442 non-null float64 21 DO...L. 442 non-null float64 22 Chl.a...... 442 non-null float64 23 유해남조류.세포수..cells... 442 non-null float64 24 Microcystis 442 non-null float64 25 Anabaena 442 non-null float64 26 Oscillatoria 442 non-null float64 27 Aphanizomenon 442 non-null float64 dtypes: float64(24), object(4) memory usage: 100.1+ KB C:\Users\손보건\AppData\Local\Temp\ipykernel_33160\536553338.py:1: FutureWarning: DataFrame.interpolate with object dtype is deprecated and will raise in a future version. Call obj.infer_objects(copy=False) before interpolating instead. df = df.interpolate(method='time')
전처리 확인
df.columns
Index(['중권역.명', '측정소.명', '생물화학적산소요구량.BOD.', '클로로필.a.Chlorophyll.a.', '화학적산소요구량.COD.', '용존산소.DO.', '용존총질소.DTN.', '용존총인.DTP.', '전기전도도.EC.', '분원성대장균군', '질산성질소.NO3.N.', '수소이온농도.pH.', '부유물질.SS.', '총대장균군', '총질소.T.N.', '총유기탄소.TOC.', '총인.T.P.', 'TYPE', 'PTNO', '수온...', 'pH', 'DO...L.', 'Chl.a......', '유해남조류.세포수..cells...', 'Microcystis', 'Anabaena', 'Oscillatoria', 'Aphanizomenon'], dtype='object')
df
중권역.명 측정소.명 생물화학적산소요구량.BOD. 클로로필.a.Chlorophyll.a. 화학적산소요구량.COD. 용존산소.DO. 용존총질소.DTN. 용존총인.DTP. 전기전도도.EC. 분원성대장균군 ... PTNO 수온... pH DO...L. Chl.a...... 유해남조류.세포수..cells... Microcystis Anabaena Oscillatoria Aphanizomenon 채수.일자 2016-01-04 낙동밀양 함안 3.6 10.1 5.7 14.5 2.429 0.018 364.0 1.0 ... 2020A32 5.5 8.2 14.5 10.1 242.0 0.0 0.0 0.0 0.0 2016-01-11 낙동밀양 함안 1.8 17.5 5.8 13.9 2.846 0.018 451.0 1.0 ... 2020A32 4.8 7.8 13.9 17.5 152.0 0.0 0.0 0.0 0.0 2016-01-18 낙동밀양 함안 1.3 12.1 5.7 12.9 3.122 0.016 452.0 0.0 ... 2020A32 4.0 7.9 12.9 12.1 254.0 0.0 0.0 0.0 0.0 2016-02-01 낙동밀양 함안 1.1 7.0 5.1 13.7 2.984 0.015 417.0 3.0 ... 2020A32 2.4 7.5 13.7 7.0 0.0 0.0 0.0 0.0 0.0 2016-02-11 낙동밀양 함안 1.9 12.8 5.3 12.0 2.617 0.013 324.0 0.0 ... 2020A32 4.0 7.4 12.0 12.8 0.0 0.0 0.0 0.0 0.0 ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 2024-12-16 낙동밀양 함안 2.0 20.3 5.2 13.6 2.667 0.017 319.0 28.0 ... 2020A32 7.1 8.6 13.5 24.2 1844.0 60.0 65.0 0.0 1719.0 2024-12-23 낙동밀양 함안 2.8 32.4 6.2 15.0 2.708 0.018 338.0 19.0 ... 2020A32 5.3 9.0 15.0 41.6 2386.0 0.0 12.0 0.0 2374.0 2025-01-06 낙동밀양 함안 1.6 14.7 4.5 13.6 2.716 0.011 355.0 17.0 ... 2020A32 4.7 8.2 13.5 18.9 436.0 0.0 0.0 0.0 436.0 2025-01-13 낙동밀양 함안 1.3 10.4 4.7 13.1 2.910 0.014 364.0 9.0 ... 2020A32 3.2 8.1 12.9 11.8 522.0 0.0 0.0 0.0 522.0 2025-02-24 낙동밀양 함안 2.3 17.5 4.9 13.9 3.180 0.008 401.0 7.0 ... 2020A32 4.6 8.1 13.6 14.2 0.0 0.0 0.0 0.0 0.0 442 rows × 28 columns
계절성 뚜렷: 6~8월 여름철 남조류 세포 수 급증
2018년 급등: 실제 환경 변화(수문 개방 정책, 기온 상승 등) 반영
상관분석:
양의 상관: Chlorophyll-a, 총인(TP), 총질소(TN)
음의 상관: 용존산소(DO)
STL 분해:
추세: 관리 정책 반영 시 하락세
계절성: 매년 동일 패턴 반복

4. 모델링

전통 시계열

SARIMA(2,0,1)(1,0,0,48)
Lag: 1주
Recursive Forecast 방식 → Direct Forecast 대비 MAE 감소

통계 기반 ML

Prophet (Meta AI)
비정상성·계절성 자동 반영
MAE: 약 24,094

딥러닝

모델
구조
주요 설정
성능(Val Loss)
FNN
Dense(256→64→1)
lr=0.001, MSE
0.08
CNN
Conv1D→GAP→Dense
lr=0.0005, MSE, window=7
0.03
RNN
SimpleRNN(64→32→1)
lr=0.0005, MSE
0.07
LSTM
LSTM(128→Dense(1))
lr=0.001, Huber, window=5
0.05
Transformer
Dense 다층 + Dropout
lr=0.01
0.3056

5. 문제 해결 전략

문제
해결 방법
효과
낮은 예측력, 스파이크 미검출
시차·롤링·사이클릭 변수, 외생변수 추가
MAE 10% 개선
분포 왜곡·극단치 영향
로그 변환 + Huber/Quantile Loss
MAE 15% 감소
재귀 예측 오차 누적
Direct Multi-step Seq2Seq
예측 안정성 확보
단일 모델 편향
Weighted 앙상블 + Stacking 메타러닝
MAE 20% 개선, 스파이크 재현 ↑

6. 프로젝트 결과

최종 모델: Stacking 앙상블 (CNN+LSTM+Prophet)
성능: 단일 모델 대비 MAE 약 20% 개선
활용 가능성:
실시간 경보 시스템(위험 수치 도달 시 경보)
환경 정책 사전 시뮬레이션
농업·양식업 생산 계획 지원