Korea Ocean Water Quality Information¶
from https://www.nifs.go.kr (National Institute of Fisheries Science)¶
api service https://www.nifs.go.kr/OpenAPI_json¶
In [40]:
%use kandy
%use dataframe
%use lets-plot
In [2]:
var df_stat_raw = DataFrame.readJson("http://192.168.35.107:7788/nifs/stat")
df_stat_raw.describe()
Out[2]:
DataFrame: rowsCount = 7, columnsCount = 10
| name | type | count | unique | nulls | top | freq | min | median | max |
|---|---|---|---|---|---|---|---|---|---|
| gru_nam | String | 936 | 3 | 0 | 남해 | 528 | 남해 | 남해 | 서해 |
| sta_cde | String | 936 | 39 | 0 | fgg4c | 24 | bgj8a | ftdk5 | wn087 |
| sta_nam_kor | String | 936 | 39 | 0 | 거제 가배 | 24 | 강릉 | 영덕 | 해남 임하 |
| obs_datetime | String | 936 | 24 | 0 | 26-06-07 13 | 39 | 26-06-07 13 | 26-06-08 00 | 26-06-08 12 |
| tmp_min | String | 936 | 89 | 0 | 18.9 | 49 | 0.2 | 18.9 | 22.9 |
| tmp_max | String | 936 | 88 | 0 | 19.1 | 51 | 0.2 | 19.0 | 23.0 |
| tmp_avg | String | 936 | 150 | 0 | 18.60 | 35 | 0.20 | 18.95 | 22.95 |
In [3]:
var df_stat = df_stat_raw.convert { tmp_min and tmp_max and tmp_avg }.with { it.toFloat() }
df_stat.describe()
Out[3]:
DataFrame: rowsCount = 7, columnsCount = 12
| name | type | count | unique | nulls | top | freq | mean | std | min | median | max |
|---|---|---|---|---|---|---|---|---|---|---|---|
| gru_nam | String | 936 | 3 | 0 | 남해 | 528 | null | null | 남해 | 남해 | 서해 |
| sta_cde | String | 936 | 39 | 0 | fgg4c | 24 | null | null | bgj8a | ftdk5 | wn087 |
| sta_nam_kor | String | 936 | 39 | 0 | 거제 가배 | 24 | null | null | 강릉 | 영덕 | 해남 임하 |
| obs_datetime | String | 936 | 24 | 0 | 26-06-07 13 | 39 | null | null | 26-06-07 13 | 26-06-08 00 | 26-06-08 12 |
| tmp_min | Float | 936 | 89 | 0 | 18.900000 | 49 | 18.363034 | 3.366031 | 0.200000 | 18.900000 | 22.900000 |
| tmp_max | Float | 936 | 88 | 0 | 19.100000 | 51 | 18.469444 | 3.375458 | 0.200000 | 19.000000 | 23.000000 |
| tmp_avg | Float | 936 | 150 | 0 | 18.600000 | 35 | 18.416239 | 3.369988 | 0.200000 | 18.950001 | 22.950001 |
In [30]:
df_stat.filter { gru_nam == "서해" }.plot{
layout {
title = "일별 해수 온도 변화"
size = 1500 to 400
}
x("obs_datetime"){
axis.name = "관측일시"
}
y {
axis {
limits = tmp_min.min().toInt().minus(1)..tmp_max.max().toInt().plus(1)
name = "Temperature °C"
}
}
line {
y(tmp_avg)
color("sta_nam_kor"){
legend.name = "관측소명"
}
type = LineType.DOTTED
width = 0.6
}
ribbon {
yMin("tmp_min")
yMax("tmp_max")
fillColor("sta_nam_kor") {
legend.name = "관측소명"
}
alpha = 0.6
borderLine.width = 0.0
}
}
Out[30]:
In [75]:
val data_Ribbon = df_stat.filter { gru_nam == "서해" }.toMap()
letsPlot(data_Ribbon) +
geomRibbon(alpha = 0.2){
x="obs_datetime"
ymin = "tmp_min"
ymax = "tmp_max"
fill = "sta_nam_kor"
} +
geomLine( ) {x="obs_datetime"; y="tmp_avg"; color="sta_nam_kor"} +
scaleYContinuous(limits=Pair(10, 25) ) +
labs(x="관측일시", y="Temperature °C") +
theme(
axisTextX= elementText( angle=45),
) +
ggsize(1500, 400)
Out[75]:
In [97]:
val data_Ribbon = df_stat.filter { gru_nam == "동해" }.toMap()
letsPlot(data_Ribbon) +
geomRibbon(alpha = 0.2){
x="obs_datetime"
ymin = "tmp_min"
ymax = "tmp_max"
fill = "sta_nam_kor"
} +
geomLine( ) {x="obs_datetime"; y="tmp_avg"; color="sta_nam_kor"} +
scaleYContinuous(limits=Pair(10, 25) ) +
labs(x="관측일시", y="Temperature °C") +
theme(
axisTextX= elementText( angle=45),
) +
ggsize(1500, 400)
Out[97]:
In [100]:
val data_Ribbon = df_stat.filter { gru_nam == "남해" }.toMap()
letsPlot(data_Ribbon) +
geomRibbon(alpha = 0.2){
x="obs_datetime"
ymin = "tmp_min"
ymax = "tmp_max"
fill = "sta_nam_kor"
} +
geomLine( ) {x="obs_datetime"; y="tmp_avg"; color="sta_nam_kor"} +
scaleYContinuous(limits=Pair(14, 25) ) +
labs(x="관측일시", y="Temperature °C") +
theme(
axisTextX= elementText( angle=45),
) +
ggsize(1500, 600)
Out[100]:
In [32]:
val df_day = df_day_raw.convert { wtr_tmp }.with { it.toFloat() }
df_day.describe()
Out[32]:
DataFrame: rowsCount = 8, columnsCount = 12
| name | type | count | unique | nulls | top | freq | mean | std | min | median | max |
|---|---|---|---|---|---|---|---|---|---|---|---|
| sta_cde | String | 2970 | 39 | 0 | fth59 | 144 | null | null | bgj8a | fsch6 | wn087 |
| sta_nam_kor | String | 2970 | 39 | 0 | 통영 학림 | 144 | null | null | 강릉 | 여수 신월 | 해남 임하 |
| obs_datetime | String | 2970 | 48 | 0 | 2026-06-07 13:30:00 | 62 | null | null | 2026-06-07 13:30:00 | 2026-06-08 01:30:00 | 2026-06-08 13:00:00 |
| obs_lay | String | 2970 | 3 | 0 | 1 | 1869 | null | null | 1 | 1 | 3 |
| wtr_tmp | Float | 2970 | 183 | 0 | 18.900000 | 116 | 17.205488 | 4.111974 | 0.200000 | 18.600000 | 23.000000 |
| gru_nam | String | 2970 | 3 | 0 | 남해 | 1440 | null | null | 남해 | 동해 | 서해 |
| lon | Double | 2970 | 39 | 0 | 128.415100 | 144 | 127.762842 | 1.178954 | 124.729500 | 128.148000 | 129.549700 |
| lat | Double | 2970 | 39 | 0 | 34.749800 | 144 | 35.711531 | 1.399781 | 33.310400 | 34.834800 | 38.368100 |
In [92]:
df_day.filter { gru_nam == "동해" }.plot {
layout {
size = 800 to 400
x.axis.name = "Observatory"
y.axis.name = "Temperature"
}
boxplot(sta_nam_kor, wtr_tmp) {
boxes {
fillColor(Stat.middle) {
legend{
name = "Temperature "
breaks(format = "{d}°C")
}
scale = continuous(Color.BLUE..Color.RED)
}
// Border line color depends on `x` category
borderLine.color(Stat.x)
// `identity` position, i.e boxes are overlapping
}
outliers {
// points context + StatBoxplotOutliers context
// color depends on `x` category
color(Stat.x){
legend.name = "Observatory"
}
}
}
}
Out[92]:
In [91]:
val data_stat = df_day.filter { gru_nam == "동해" }.toMap()
val sta_nam_korByMiddle = asDiscrete("sta_nam_kor", orderBy = "..middle..", order = 1)
letsPlot( data_stat) { y = "wtr_tmp"} +
scaleColorViridis(option = "C", end = 0.8) +
scaleYContinuous(limits=Pair(0, 25) ) +
geomBoxplot(){ x=sta_nam_korByMiddle ; color = "..middle.."} +
labs(y="온도 °C", x="관측지점", color ="온도 °C") +
ggsize( width = 800, height = 400)
Out[91]:
In [93]:
val data_stat = df_day.filter { gru_nam == "서해" }.toMap()
val sta_nam_korByMiddle = asDiscrete("sta_nam_kor", orderBy = "..middle..", order = 1)
letsPlot( data_stat) { y = "wtr_tmp"} +
scaleColorViridis(option = "C", end = 0.8) +
scaleYContinuous(limits=Pair(0, 25) ) +
geomBoxplot(){ x=sta_nam_korByMiddle ; color = "..middle.."} +
labs(y="온도 °C", x="관측지점", color ="온도 °C") +
ggsize( width = 800, height = 400)
Out[93]:
In [96]:
val data_stat = df_day.filter { gru_nam == "남해" }.toMap()
val sta_nam_korByMiddle = asDiscrete("sta_nam_kor", orderBy = "..middle..", order = 1)
letsPlot( data_stat) { y = "wtr_tmp"} +
scaleColorViridis(option = "C", end = 0.8) +
scaleYContinuous(limits=Pair(0, 25) ) +
geomBoxplot(){ x=sta_nam_korByMiddle ; color = "..middle.."} +
labs(y="온도 °C", x="관측지점", color ="온도 °C") +
ggsize( width = 1500, height = 400)
Out[96]:
In [35]:
var df_current_raw = DataFrame.readJson("http://192.168.35.107:7788/nifs/seawaterinfo/current")
df_current_raw.describe()
Out[35]:
DataFrame: rowsCount = 8, columnsCount = 12
| name | type | count | unique | nulls | top | freq | mean | std | min | median | max |
|---|---|---|---|---|---|---|---|---|---|---|---|
| sta_cde | String | 62 | 39 | 0 | bgna3 | 3 | null | null | bgj8a | fnm5b | wn087 |
| sta_nam_kor | String | 62 | 39 | 0 | 강릉 | 3 | null | null | 강릉 | 여수 신월 | 해남 임하 |
| obs_datetime | String | 62 | 1 | 0 | 2026-06-08 13:00:00 | 62 | null | null | 2026-06-08 13:00:00 | 2026-06-08 13:00:00 | 2026-06-08 13:00:00 |
| obs_lay | String | 62 | 3 | 0 | 1 | 39 | null | null | 1 | 1 | 3 |
| wtr_tmp | String | 62 | 43 | 0 | 19.1 | 4 | null | null | 0.2 | 18.8 | 9.7 |
| gru_nam | String | 62 | 3 | 0 | 남해 | 30 | null | null | 남해 | 동해 | 서해 |
| lon | Double | 62 | 39 | 0 | 128.949200 | 3 | 127.762608 | 1.190227 | 124.729500 | 128.148000 | 129.549700 |
| lat | Double | 62 | 39 | 0 | 37.799000 | 3 | 35.713669 | 1.410566 | 33.310400 | 34.854700 | 38.368100 |
In [36]:
var df_current = df_current_raw.convert { wtr_tmp }.with { it.toFloat() }
df_current.describe()
Out[36]:
DataFrame: rowsCount = 8, columnsCount = 12
| name | type | count | unique | nulls | top | freq | mean | std | min | median | max |
|---|---|---|---|---|---|---|---|---|---|---|---|
| sta_cde | String | 62 | 39 | 0 | bgna3 | 3 | null | null | bgj8a | fnm5b | wn087 |
| sta_nam_kor | String | 62 | 39 | 0 | 강릉 | 3 | null | null | 강릉 | 여수 신월 | 해남 임하 |
| obs_datetime | String | 62 | 1 | 0 | 2026-06-08 13:00:00 | 62 | null | null | 2026-06-08 13:00:00 | 2026-06-08 13:00:00 | 2026-06-08 13:00:00 |
| obs_lay | String | 62 | 3 | 0 | 1 | 39 | null | null | 1 | 1 | 3 |
| wtr_tmp | Float | 62 | 43 | 0 | 19.100000 | 4 | 17.425806 | 3.962237 | 0.200000 | 18.600000 | 22.700001 |
| gru_nam | String | 62 | 3 | 0 | 남해 | 30 | null | null | 남해 | 동해 | 서해 |
| lon | Double | 62 | 39 | 0 | 128.949200 | 3 | 127.762608 | 1.190227 | 124.729500 | 128.148000 | 129.549700 |
| lat | Double | 62 | 39 | 0 | 37.799000 | 3 | 35.713669 | 1.410566 | 33.310400 | 34.854700 | 38.368100 |
In [37]:
df_current = df_current.insert("관측층"){
when(this.obs_lay){
"1" -> "표층"
"2" -> "중층"
"3" -> "심층"
else -> ""
}
}.after("obs_lay")
df_current.describe()
Out[37]:
DataFrame: rowsCount = 9, columnsCount = 12
| name | type | count | unique | nulls | top | freq | mean | std | min | median | max |
|---|---|---|---|---|---|---|---|---|---|---|---|
| sta_cde | String | 62 | 39 | 0 | bgna3 | 3 | null | null | bgj8a | fnm5b | wn087 |
| sta_nam_kor | String | 62 | 39 | 0 | 강릉 | 3 | null | null | 강릉 | 여수 신월 | 해남 임하 |
| obs_datetime | String | 62 | 1 | 0 | 2026-06-08 13:00:00 | 62 | null | null | 2026-06-08 13:00:00 | 2026-06-08 13:00:00 | 2026-06-08 13:00:00 |
| obs_lay | String | 62 | 3 | 0 | 1 | 39 | null | null | 1 | 1 | 3 |
| 관측층 | String | 62 | 3 | 0 | 표층 | 39 | null | null | 심층 | 표층 | 표층 |
| wtr_tmp | Float | 62 | 43 | 0 | 19.100000 | 4 | 17.425806 | 3.962237 | 0.200000 | 18.600000 | 22.700001 |
| gru_nam | String | 62 | 3 | 0 | 남해 | 30 | null | null | 남해 | 동해 | 서해 |
| lon | Double | 62 | 39 | 0 | 128.949200 | 3 | 127.762608 | 1.190227 | 124.729500 | 128.148000 | 129.549700 |
| lat | Double | 62 | 39 | 0 | 37.799000 | 3 | 35.713669 | 1.410566 | 33.310400 | 34.854700 | 38.368100 |
In [39]:
df_current
.select{sta_nam_kor and wtr_tmp and "관측층"}
.plot {
layout {
title = "관측 층별 해수 온도"
size = 1500 to 400
xAxisLabel = "Observatory"
yAxisLabel = "Temperature( °C )"
}
bars {
x(sta_nam_kor)
y(wtr_tmp){
scale = continuous(wtr_tmp.min().minus(1)..wtr_tmp.max().plus(1))
}
fillColor("관측층") {
legend{
name = "Ocean Layer"
}
scale = categoricalColorHue()
}
}
}
Out[39]:
In [61]:
val data = df_current.select{sta_nam_kor and wtr_tmp and "관측층"}
letsPlot(data.toMap()) { x = "sta_nam_kor" } +
geomBar( position = positionDodge(), alpha = 0.5 ) {
fill = "관측층"
weight = "wtr_tmp"
} +
labs( title="관측 층별 해수 온도", y="Temperature( °C )", x="Observatory", fill="Ocean Layer" ) +
theme( axisTextX= elementText( angle=45) ) +
scaleYContinuous(limits=Pair(0, 25) ) +
ggsize( width = 1500, height = 400)
Out[61]:
In [ ]: