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

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/174615_WBEOHRMmeZ4ytzHowY?q=80&s=1280x180&t=outside&f=webp)

### Data & Code : 

[GitHub - Sonbori/est5_water](https://github.com/Sonbori/est5_water)

# 1. 프로젝트 개요

**목표**

- 낙동강 유역의 **유해 남조류 세포 수**를 시계열로 예측하는 AI 모델 개발

- 수질 및 환경 요인을 기반으로 **녹조 발생 경보 시스템** 기초 모델 구축

- 환경 정책·수질 관리·농업 및 양식업 대응에 활용 가능한 분석 프레임워크 제공

**배경**

- 남조류 과다 증식은 여름철에 심각한 수질 악화를 유발하며, 독소 생성 시 인체와 생태계에 위협

- 기존 연구들은 짧은 기간·단일 지역·제한된 지표만 사용 → 장기간, 다변량, 시계열 모델 필요

---

# 2. 데이터 구축 과정✨

**요약**

- 녹조 데이터셋의 어려운 점은, X(설명 변수, 수질 데이터셋)와 Y(목표 변수, 녹조 데이터셋) 두 데이터가 완전히 따로 놀아서, 결합하기 어렵다는 점.

    - X의 측정점은 낙동강에서만 260개가 넘고, Y의 측정점은 36개인데 둘은 완전 별개

- 가장 가능성 있는 결합 방법은, 위치에 기반해 가까운 지역에서 측정한 수질 정보와 녹조 정보를 공간 상에서 매칭하는 것

    - 하지만 두 데이터셋이 측정 베이스임에도, 측정 지점을 전혀 공유하지 않음

    - 심지어 주소나 구체적 위치 정보도 없음

- 이에, 각각의 측정 지점 정보를 다른 데이터셋 등에서 찾아 결합하고, 주소만 있는 경우는 지오코딩을 해 위치 정보를 붙여 넣어 X, Y 데이터셋을 완성

- 하지만 실폭하천 지도에 플로팅해보니... 녹조 발생 지역과 관계없는 X 지역도 많음을 확인.

    - 원래 계획은 각 X 측정소 별로 가장 가까운 Y 녹조 측정소를 매칭하려 했지만,

    - 이 정도면 가까운 지역만 추려서(거리 바탕으로) X 측정소를 한정한 뒤 진행하는 게 맞을 듯

        - 아래는 10km 반경 지역 추출하는 모습

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/174819_crlB8fUVEaxfxJYdCF?q=80&s=1280x180&t=outside&f=webp)

---

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

### 후보 데이터셋:

1. **물환경정보시스템 - 과거수질자료** -> 녹조 TGT(유해남조류세포수, 마이크로시스티스 등 포함).
1. [https://water.nier.go.kr/web/algaePreMeasure?pMENU_NO=111](https://water.nier.go.kr/web/algaePreMeasure?pMENU_NO=111)

2. **물환경정보시스템 - 수질측정망** -> 60여 개 특성 존재
2. [https://water.nier.go.kr/web/board/29/?pMENU_NO=572](https://water.nier.go.kr/web/board/29/?pMENU_NO=572)

3. **물환경정보시스템 - 자동측정망: 확정자료 조회** -> 2012년 이후 자료 존재. 엽록소 항목 참조 가능
3. [https://water.nier.go.kr/web/autoMeasure/confirm?pMENU_NO=576](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](https://www.data.go.kr/data/15111317/fileData.do))

    - **전국 실폭하천 지도에 1200여 측정소 위치 표시**

    - **목표 변수(Y, 녹조 데이터셋)에 존재하는 측정소 이름만 추림(낙동강 수계): 264개**

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/174911_v9oXtV19kk8p8YiR1I?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/174927_r2UN6wQFo33X3tdpjx?q=80&s=1280x180&t=outside&f=webp)

  

    - 도심 하천 구간을 포함한 전국 1254개 수질 측정소(2023년 기준) 가운데 낙동강수계에 260곳 이상 몰려 있음(위 그림). 이들 위치 정보를 이용해 수질측정망 위치를 확정

    - **주의:** 금천, 사천천, 한천 등 동명 하천 4곳 정보 정제 뒤 시행

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/174942_hC8qIKKGSknumaKQcA?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/174949_wZa4yzaVIbf4dmv6v8?q=80&s=1280x180&t=outside&f=webp)

~이런 데이터를 보면 '어쩌라고' 이런 생각이..~

> 수동 정제함: 총 261개로 정리

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

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175000_B9PpiebRmbGpj0VUv0?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175007_tIK3Dfbuuj2S2dSh2k?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175022_U3CXVPGfSqp1YDk2b0?q=80&s=1280x180&t=outside&f=webp)

- 타깃에 해당하는 녹조 측정 장소 36곳은 측정소 주소 정보를 홈페이지에서 구한 뒤,

- 이를 kakao map api를 이용해 위도, 경도 좌표로 변환(지오코딩)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175047_9uKzAoCdyytUy2XWq5?q=80&s=1280x180&t=outside&f=webp)

- 지오코딩 성공

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175057_v5DMrcaZ0e3oa40ebd?q=80&s=1280x180&t=outside&f=webp)

-   X 및 Y 데이터 바탕으로 둘 동시에 지도에 표시해보면

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175105_Vu25U42EYo4nWa7Vz5?q=80&s=1280x180&t=outside&f=webp)

## Part 3. X - Y 매칭

- Y에 해당하는 녹조 측정이 이뤄진 36개 측정소, 1만 2000여 데이터와 관련이 있을 것으로 추정되는 X 측정소는 소수임.

- **관련 있는 X(수질 측정소 데이터)를 추려야 함: 거리 기반**

    - 방법 1: 녹조 측정 지점 반경 x km 지점을 정해 공간 연산 통해 관련 있는 수질 측정소를 추린 뒤 거리 기반 매칭

    - 방법 2: 그냥 거리 기반으로 매칭하되, 일정 거리 이상은 제거

    - 아래는 녹지 측정소 반경 10km, 5km 이내를 표시한 것

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175118_VTipu2zQQ66R2zTKLo?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175129_TjhpTkfAILklFQaCZn?q=80&s=1280x180&t=outside&f=webp)

> 10km일 경우: 204개 측정소 2만 3000여 데이터 선정

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175140_Lti72wAZFkdzvaf9nG?q=80&s=1280x180&t=outside&f=webp)

> 5km 일 경우: 93개 측정소 1만 5000여 데이터 선정

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175148_jhsgTTyntJkNj8VqbM?q=80&s=1280x180&t=outside&f=webp)

- 어떻게 매칭시킬까

    - 방법 1: 각 수질 측정소(X)를 가장 가까운 녹조 측정소(Y)와 연결

        - —> 가장 간단하지만, 현실을 잘 반영할지 의문

    - 방법 2: 각 녹조 측정소(Y) 주변의 정해진 반경(5km 또는 10km) 속 수질 측정소(X)를 모두 Y에 매칭

        - —> 주변 일정한 거리의 수질 정보를 다 반영할 수 있어 유리.

        - 단, 방법은 고민해야

        - **<<현재까지의 파일>>**

        -   [X_2.csv](https://attachment:646a195e-04e6-4894-8150-3e4f80f399a0:X_2.csv)

        -   [Y.csv](https://attachment:e0bac104-a6d0-43c3-9d97-3077eeb0b04a:Y.csv)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175155_5LV9Dwdckb35yolid4?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175206_P3sNgWltKk2EmzjHWD?q=80&s=1280x180&t=outside&f=webp)

  

  매칭 결과: 근데 이렇게 하면 너무 많은 데이터가 나올 듯… 추려야 하나?

```javascript
         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로 해보면 어떨지

```javascript
                               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)

    -   [XY_cnhaman_1vs1_3.csv](https://attachment:252cd9d8-9d47-4bba-9161-7b120be30989:XY_cnhaman_1vs1_3.csv)

        - 동일하게 일주일 간격으로 채수한 데이터이고

        - 날짜로 거의 비슷

    - 위 내용 바탕으로 피처를 선택한 뒤, 반경 10km 구간 내 모든 수질 측정소 데이터를 이용해 선택 피처 평균값으로 녹조 발생을 예측하는 것도 가능할 듯

- 참고로 전에 Jena로 만들었던 NN CNN 초기 코드에 위 파일 넣어본 사례

-   [ga_nn_multi.ipynb](https://attachment:5b184bd3-bc5a-4841-8992-8327a995d702:ga_nn_multi.ipynb)

---

# TODO

- 수질 + 녹조 merge

    - 위치 정보

    - 시간 정보

- 녹조 예측에 중요한 feature 확인

    - 4종류의 녹조 세포? 확인

    - 녹조에 영향을 미치는 요소 확인

        - 수온

        - 오염수

        - 농약

        - 등등..

    - 보/댐 정보 확인

        - 유수량이 많으면 어쩌구… 녹조 발생률 높다고 한 것같음

  → 도메인 조사를 바탕으로 이러한 요소로 EDA 수행
  

- 코드는 다 있으니까… merge랑 도메인 조사만 어떻게 하면 금방 끝날듯…

- 그리고 녹조 경보 기준이 있는 거 같은데 그거 확인

- 그렇다면 근접한 위치를 어떻게 묶을까?

    - 위치 정보

        - 우리의 관심 지역은 낙동강

1. 때문에 낙동강을 기준으로 녹조 정보를 수집한 위치 목록을 추출

2. 위 정보를 기반으로, 특정 거리 이내에 수질측정망를 수집한 위치가 있는지 list up

3. 근접한 관측소가 추출

    - 시간 정보

        - 수집된 시점이 동일하지 않음

        - 수집된 시기를 확인해서 적당한 범위 내에 up sampling 수행

- 녹조 측정 위치를 기준으로 공통된 위치를 merge

    - 수질측정망 측정 위치와, 녹조정보 측정 위치가 겹치는 것 뿐만아니라

    - 강의 상/하류를 정보를 포함하여 더 공통된 위치를 추출할 수도 있겠지만

    - 그렇게 하기에는 더 심화된 도메인 지식 공부가 필요함

    - 현재는 위 상황까지 고려한 작업을 수행하기에는 시간이 부족한 상황

    - 단순히 특정 거리 이내에 겹치는 지점을 기준으로 merge 하는 것이 최선이라고 생각

- 환경부 국립환경과학원에서 제공하는 물 환경 대시보드 GUI

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175304_lukgpsqnuXm27I3mEf?q=80&s=1280x180&t=outside&f=webp)

- 수질측정망(261)과 녹조정보(26)의 수집 위치가 각각 다름

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175311_UNxBwb1BYjCymrXblk?q=80&s=1280x180&t=outside&f=webp)

- 대부분은 수질측정망과 녹조정보가 수집된 위치가 근접해 있지만, 일부 아닌 곳도 존재

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175317_SS6gWoI6NDgdNgqZCE?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175323_6N8FpLom6WTOcIZIc2?q=80&s=1280x180&t=outside&f=webp)

- ㅇㅋ 그러면 이제 지역을 묶어야함

- 위치 정보를 어떻게 가져오지?

    - 수질측정망

        - 일단 수질측정망 위치정보를 제공하는 shp파일이 있음

        - 근데 수질측정망 데이터셋에 위도경도 가 있긴함

        - shp파일은 필요없을듯?

    - 녹조정보

        - 26에 대한 녹조정보 수집 위치 추출해야할듯

| 낙동강(해평) | 경상북도 칠곡군 석적읍 중지리 | 낙동강물환경연구소 |
| --- | --- | --- |
| 낙동강(칠서) | 경상남도 함안군 칠북면 이령리 | 낙동강물환경연구소 |
| 운문호(댐앞) | 경상북도 청도군 운문면 대천리 | 대구지방환경청 |
| 운문호(취수탑2) | 경상북도 청도군 운문면 서지리 | 대구지방환경청 |
| 영천호(취수탑) | 경상북도 영천시 자양면 삼귀리 | 대구지방환경청 |
| 진양호(내동) | 경상남도 사천시 곤명면 연평리 | 낙동강유역환경청 |
| 안계호(취수탑) | 경상북도 경주시 강동면 안계리 | 대구지방환경청 |
| 공산지(중앙부) | 대구광역시 동구 지묘동 | 대구시 상수도사업본부 |
| 공산지(취수탑) | 대구광역시 동구 지묘동 | 대구시 상수도사업본부 |
| 진전지(상류) | 경상북도 포항시 남구 연일읍 오천리 | 경상북도 보건환경연구원 |
| 진전지(하류) | 경상북도 포항시 남구 연일읍 오천리 | 경상북도 보건환경연구원 |
| 사연호(취수탑) | 울산광역시 울주군 범서읍 사연리 | 낙동강유역환경청 |
| 사연호(반연리) | 울산광역시 울주군 언양읍 반연리 | 낙동강유역환경청 |
| 덕동호(댐앞) | 경상북도 경주시 덕동 | 낙동강물환경연구소 |
| 낙동강(물금·매리) | 경상남도 김해시 상동면 감노리 | 낙동강물환경연구소 |
| 진양호(판문) | 경상남도 진주시 판문동 | 낙동강유역환경청 |
| 낙동강(강정·고령) | 대구광역시 달성군 다사읍 죽곡리 | 낙동강물환경연구소 |

        - 근데 26개가 아님… 뭐야 진짜

### 📍 도메인 조사

[녹조란 무엇인가?](https://www.notion.so/24d7c390940480f0bb70d43f4b46e783?pvs=21)

---

# 3. EDA & Preprocess 주요 인사이트

[EDA.ipynb](https://attachment:8e505955-8f2c-4dbf-b010-c228f136ddaa:EDA.ipynb)

```javascript
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()
```

```javascript
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()
```

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175429_w6YgKL67RSJ2CdMDWX?q=80&s=1280x180&t=outside&f=webp)

```javascript
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')
```

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175852_UuplabtKcUMDy1wHwx?q=80&s=1280x180&t=outside&f=webp)

---

🛠️Feature Engeneering

```javascript
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 분포

```javascript
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()
```

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175932_aA8fmPBvTp1pafDnQc?q=80&s=1280x180&t=outside&f=webp)

### 월별 Chl-a 분포

```javascript
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()
```

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/175955_OEpGtaWtGeK0rq4RbN?q=80&s=1280x180&t=outside&f=webp)

### 계절별 Chl-a 분포

```javascript
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()
```

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180007_WpPS0FXVwo2bWkmPID?q=80&s=1280x180&t=outside&f=webp)

---

# CCF**(Cross-Correlation Fuction)**

- 시계열 A와 시계열 B 간에, 서로 시간적으로 얼마나 앞서거나 뒤처졌을 때 가장 높은 상관관계를 보이는지를 분석하는 도구.

```javascript
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)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180024_TDuBuB7u8aXRsStYeB?q=80&s=1280x180&t=outside&f=webp)

---

```javascript
# 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} → 유해남조류 수')

```

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180043_eBnCSlZAYZ2veagaVc?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180047_pnKVtY0NuxIJuOYWJU?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180051_j6PmhN1Xa5OylUplnB?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180054_gJoequJFdtuM3YSK5Q?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180059_7puaxEOeXtOUA6fTLC?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180102_HQJR99qpSP3d1DGbsK?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180106_JACXnczvfb78V8KiFS?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180110_6yzE5fTRc5JHz1HFgE?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180113_1ZjccbDL0CWKD4sqpQ?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180116_wZotwrECe4k52L52Zx?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180119_7l6GcaEJeHSZy7bvjS?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180122_Bhni9NNSBRTcNb7h5t?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180125_rXF8KvnwbtteAL3kAz?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180128_N9ubvXHqLbSFo5NRdh?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180132_Vgt41sZ9UdU17Tlhl8?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180135_mgr27VjJIhZRnk1b3C?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180137_bDfnsR3MHRp7BL9WHT?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180141_bqTcEfkihGYsj8Nts2?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180144_N4kb16XxiZxgxKm0oL?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180147_MdVK3rx3CFIGjudwjn?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180151_LbfzGzycv4eL4IeXcE?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180154_tPnwY6CcBdmPBju3nV?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180158_O4wsdMJeOJyfS1DJRe?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20260116/180201_Ad5N7SLoguIJKxk4Ey?q=80&s=1280x180&t=outside&f=webp)

```javascript
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
```

```javascript
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()
```

- 

```javascript
paths = 'Datasets/XY_cnhaman_1vs1_3.csv'
df = pd.read_csv(paths, index_col=0)
df.head()
```

```javascript

연도	월	조사회차	채수.일자	수계.명	중권역.명	측정소.명	수심	음이온계면활성제.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
```

---

- 무의미한 열 제거

```javascript
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()
```

```javascript

중권역.명	측정소.명	수심	음이온계면활성제.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으로 채우는 작업 수행

```javascript
zero_cols = ["Microcystis", "Anabaena", "Oscillatoria", "Aphanizomenon"]
df[zero_cols] = df[zero_cols].fillna(0)
```

---

## 결측치 제거

- 결측치 개수를 기반으로 threshold를 설정

- 임계치 기반으로 결측치가 많은 열을 제거

- 제거되지 않은 열은 보간법 등을 통해 결측치 처리

```javascript
df.info()
```

```javascript
<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
- 측정 값 이다 보니까 -> 검출되지 않았다.

```javascript
df = df.dropna(axis=0, thresh=20)
df.info()
```

```javascript
<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
```

```javascript
df = df.dropna(axis=1, how='any')
df.info()
```

```javascript
<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
```

```javascript
df = df.interpolate(method='time')
df.info()
```

```javascript
<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')
```

---

- 전처리 확인

```javascript
df.columns
```

```javascript
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')
```

```javascript
df
```

```javascript

중권역.명	측정소.명	생물화학적산소요구량.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

## 딥러닝

[predicted](https://www.notion.so/predicted-24d7c39094048033b92af4e38dae3aa3?pvs=21)

[project](https://www.notion.so/project-24d7c39094048054aff8f37a8fc248ce?pvs=21)

| 모델 | 구조 | 주요 설정 | 성능(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% 개선

- **활용 가능성**:

    - 실시간 경보 시스템(위험 수치 도달 시 경보)

    - 환경 정책 사전 시뮬레이션

    - 농업·양식업 생산 계획 지원

For the site tree, see the [root Markdown](https://slashpage.com/sonbo.md).
