일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- Algorithm
- 농촌진흥청
- 유사도
- 대시보드
- streamlit
- 리눅스 crontab 사용법
- 스마트팜
- 스마트팜코리아
- DTW
- 인터랙티브 차트
- 자동화
- 시계열 시각화
- group by
- join
- 농정원
- 농림수산식품교육문화정보원
- 생육
- cron 스케줄 설정
- 리눅스 자동화 스크립트
- solvesql
- Programmers
- 주기적 실행
- ML
- 공유
- Python
- SQL
- 웹앱
- 데이터분석
- cron과 crontab 차이
- 데이터
- Today
- Total
Positive-Influence-Data
[SmartFarm] 스마트팜코리아 데이터로 생육 유사도 측정 (유사도 측정편) 본문
우리 농가의 생육 패턴은 다른 농가와 얼마나 비슷할까?
이 질문에 답을 주기 위해 필자는 생육 유사도라는 지표를 계산한다.
👉 이번 글에서는 앞서 전처리한 스마트팜 생육 데이터를 활용해, DTW(Dynamic Time Warping) 알고리즘을 이용한
생육 유사도 측정 방법을 소개한다.
✔ DTW(Dynamic Time Warping)
생육 유사도를 측정하기에 앞서 필자가 사용할 DTW(Dynamic Time Warping)에 대해 간단하게 설명하고 넘어가겠다.
📌 DTW(Dynamic Time Warping)는 시계열 데이터 간의 유사도를 측정하는 데 널리 사용되는 알고리즘이다.
예를 들어, 두 농가에서 딸기를 재배했을 때 생육 시점은 다르더라도 비슷한 성장 패턴을 보였다면,
DTW는 이런 "시점 차이를 보정" 해주면서 유사한 패턴임을 인식할 수 있다.
📌 왜 DTW를 사용하는가?
일반적인 거리 계산(예: 유클리드 거리)은 두 시계열의 동일한 시간축을 전제로 한다.
하지만 실제 생육 데이터에서는 다음과 같은 차이가 존재한다.
A 농가는 2월 초에 정식,
B 농가는 2월 말에 정식
특정 생육단계가 빠르거나 느리게 진행 또는 조사 간격이 동일하지 않음.
👉 이처럼 "타이밍이 달라도 흐름은 비슷할 수 있는" 경우가 많기 때문에,
DTW는 시점이 달라도 유사한 패턴을 정렬해서 비교할 수 있는 강력한 도구이다.
👉 알고리즘 자체에 대한 자세한 내용은 링크를 들어가 보자.
✔ 생육 데이터 유사도 측정 준비
📌 필자는 스마트팜코리아의 시설원예 데이터셋 중에서 2023년 딸기 데이터를 필자의 농가라고 생각하고 DTW를 적용해서 2023년 딸기 농가데이터 중 일부와 농촌진흥청 생육 데이터의 생육 유사도를 측정해 보겠다.
필자는 PF_0024357_01 농가의 생육 데이터를 다운로드하였다. 데이터가 어떻게 생겼는지 엑셀로 열어서 확인해 보니
조사일, 단체표준명, 단체표준영문영 이렇게 세줄이 하나의 컬럼으로 작용하는 거 같다.
필자가 판단하기에 가장 윗부분 조사일, 주차, 초장(mm)... 이게 필요할 거 같다.
엑셀에서 삭제할 수도 있지만 필자는 Python을 통해서 들어오는 생육 데이터마다 일괄적으로 적용하는 것을 목표로 하기 때문에 Python으로 프로그래밍하여 처리하겠다.
import re
list_sample_data = os.listdir("./sample_data")
sample_data = pd.read_excel(f'./sample_data/{list_sample_data[0]}', header=[0, 1, 2])
# 데이터를 불러오는데 위의 세줄(아까 언급했던 3개의 칼럼)에 대해서 모두 헤더 처리를 한다.
# 그럼 컬럼은 (초장(mm), 초장, PlantHeight) 이렇게 한 묶음이 된다.
def clean_column(col):
return re.sub(r"\(.*?\)", "", col[0]).strip()
# 컬럼을 정리하는 함수인데 위에서 불러온 (초장(mm), 초장, PlantHeight)에서 첫번째만 불러와서
# "초장(mm)" 여기에서 "초장" 이렇게 바꿔주는 함수이다.
sample_data.columns = [clean_column(col) for col in sample_data.columns]
# 각 컬럼별로 적용한다.
실행했을 때 이렇게 정리된 모습으로 나온다.
📌 필자는 딸기농가 대상으로 DTW를 측정할 예정이기 때문에 일단 딸기로 픽스, 설정하겠다.
CFG = {'crop_name':'딸기'}
if CFG['crop_name'] == '딸기':
nogjinchoen_df = crop_dfs['딸기']
hangmoks = ['초장','엽장','엽폭','엽수','엽병장','관부직경']
elif CFG['crop_name'] == '토마토':
nogjinchoen_df = crop_dfs['토마토']
hangmoks = ['초장','생장길이','줄기굵기','엽장','엽폭','엽수','화방높이']
elif CFG['crop_name'] == '방울토마토':
nogjinchoen_df = crop_dfs['방울토마토']
hangmoks = ['초장','생장길이','줄기굵기','엽장','엽폭','엽수','화방높이']
elif CFG['crop_name'] == '오이':
nogjinchoen_df = crop_dfs['오이']
hangmoks = ['초장','마디수','줄기굵기','엽장','엽폭','엽수']
elif CFG['crop_name'] == '파프리카':
nogjinchoen_df = crop_dfs['파프리카']
hangmoks = ['초장','생장길이','줄기굵기','엽장','엽폭','엽수']
👉 딸기는 초장, 엽장, 엽폭, 엽수, 엽병장, 관부직경 총 6개의 항목이 공통적이다. 이 6개의 항목을 토대로 DTW를 적용하겠다.
✔ 생육 데이터 유사도 측정
📌 DTW 전략
1. 농촌진흥청 데이터가 2018년 ~ 2021년까지 총 4개년의 데이터이므로 4년의 데이터를 먼저 쪼갠다.
2. 농촌진흥청 데이터에는 여러 개의 농가가 있으므로 연도별로 농촌진흥청 데이터의 각 항목 평균, 최대, 최소 데이터를 산출한다. (예시: 2018년 딸기 초장의 최소 150mm, 평균 200mm, 최대 350mm)
3. 비교하려는 농가(필자는 PF_0024357_01)와 주차를 일치시킨다.
(예시: PF_0024357_01 농가는 4주 차 ~ 31주 차까지 생육 데이터가 있음, 농촌진흥청 데이터도 4주 차 ~ 31주 차까지 데이터를 맞춰준다.)
4. 각 조사항목별로 비교하려는 농가와 연도별 농촌진흥청 평균 데이터와 DTW 수행(비교농가와 농촌진흥청 간의 DTW 거리산출)
5. 연도별 농촌진흥청 최대 데이터와 연도별 농촌진흥청 최소 데이터와 DTW 수행(최악의 경우, DTW 최대거리 산출)
6. 연도별로 각 조사항목의 DTW거리, 최대거리를 하나로 그룹핑하여 평균화
7. (1 - (DTW 거리 / DTW 최대거리))*100 공식으로 농촌진흥청 대비 해당 농가의 각 조사항목이 얼마나 유사한지 %로 표시
👉 DTW를 총 7단계에 거쳐서 실행해 보겠다.
❓왜 생육유사도는 (1 - (DTW 거리 / DTW 최대거리))*100 공식일까?
👉 DTW 거리 = 비교 농가와 평균 농가의 차이 정도
👉 DTW 최대거리 = 해당 항목의 최악의 차이 (비슷하지 않은 정도의 최대치)
👉 (1 - 거리비율) = 유사도로 변환
👉 ×100 = 직관적인 퍼센트(%)로 표현
👉 즉, DTW 거리 대비 최대 거리를 이용해 100에서 차이 비율을 빼면,
두 조사항목 간의 유사성을 직관적으로 퍼센트로 표현할 수 있기 때문
from tslearn.metrics import dtw, dtw_path
dtw_total = pd.DataFrame()
# DTW 정보를 저장 할 데이터 프레임
for year in nogjinchoen_df['crps_year'].unique():
temp = nogjinchoen_df[nogjinchoen_df['crps_year']==year]
# 1번 항목으로 연도별로 농촌진흥청 데이터를 쪼갠다
dtw_njc = temp.groupby(['주차'])[hangmoks].mean().reset_index() # # 농촌진흥청 데이터의 주차, 항목별 조사항목 퍙균값
dtw_njc_min = temp.groupby(['주차'])[hangmoks].min().reset_index() # 농촌진흥청 데이터의 주차, 항목별 조사항목 최소값
dtw_njc_max = temp.groupby(['주차'])[hangmoks].max().reset_index() # 농촌진흥청 데이터의 주차, 항목별 조사항목 최대값
dtw_njc.fillna(method='bfill',inplace=True)
dtw_njc_min.fillna(method='bfill',inplace=True)
dtw_njc_max.fillna(method='bfill',inplace=True)
# 2번 항목으로 농촌진흥청 데이터의 조사항목별 최소, 평균, 최대값 산출
dtw_distance = []
dtw_max_distance = []
dtw_hangmoks = []
# DTW의 거리, 최대거리, 조사항목을 넣을 리스트
for hangmok in hangmoks:
# 3번과 4번 5번 항목으로 비교하려는 농가와 농촌진흥청의 주차를 맞춰준다.
A = dtw_njc[(dtw_njc['주차']<=sample_data['주차'].max()) & (dtw_njc['주차']>=sample_data['주차'].min())][hangmok]
## 비교하려는 농가 주차의 최대값과 최소값을 찾아서 그 범위에 있는 농촌진흥청 데이터를 추출하여 A라는 변수에 넣는다.(농촌진흥청 주차, 조사항목별 평균데이터)
A_max = dtw_njc_max[(dtw_njc_max['주차']<=sample_data['주차'].max()) & (dtw_njc_max['주차']>=sample_data['주차'].min())][hangmok]
A_min = dtw_njc_min[(dtw_njc_min['주차']<=sample_data['주차'].max()) & (dtw_njc_min['주차']>=sample_data['주차'].min())][hangmok]
## 비교하려는 농가 주차의 최대값과 최소값을 찾아서 그 범위에 있는 농촌진흥청 데이터를 추출하여 A라는 변수에 넣는다.(농촌진흥청 주차, 조사항목별 최소, 최대 데이터)
B = sample_data.groupby(['주차'])[hangmok].mean()
## 비교하는 농가의 주차 조사항목별 평균 데이터
path ,distance = dtw_path(A, B)
max_path, max_distance = dtw_path(A_max, A_min)
## path = 조사하려는 농가와 농촌진흥청 데이터의 DTW
## max_path = 농촌진흥청 최소 데이터와 농촌진흥청 최대 데이터의 DTW(최악의 DTW결과)
dtw_distance.append(distance)
dtw_max_distance.append(max_distance)
dtw_hangmoks.append(hangmok)
dtw_df = pd.DataFrame({'항목':hangmoks,
'거리':dtw_distance,
'최대거리':dtw_max_distance})
# dtw_df를 만들고 데이터프레임으로 만들어줌
dtw_df['years'] = year
dtw_total = pd.concat([dtw_total, dtw_df])
# dtw_total에 dtw_df를 계속 넣어줌
dtw_calc = dtw_total.groupby(['항목'])['거리','최대거리'].mean()
# 6번 항목으로 연도별로 dtw_total에 있는 것을 하나의 평균으로 통합
dtw_calc['생육유사도'] = round((1-(dtw_calc['거리']/dtw_calc['최대거리']))*100,2)
dtw_calc.reset_index()
# 7번 항목으로 생육유사도 측정
## 생육유사도 = (1 - (각 조사항목별 거리 / 각 조사항목별 최대거리))*100 이고 소수점 2자리까지 반올림
📌 DTW 결과
항목 | 거리 | 최대거리 | 생육유사도(%) |
관부직경 | 850.858914 | 1674.362759 | 49.18 |
엽병장 | 144.919577 | 1327.416629 | 89.08 |
엽수 | 27.482090 | 226.485856 | 87.87 |
엽장 | 58.633136 | 617.418810 | 90.50 |
엽폭 | 38.818240 | 509.025882 | 92.37 |
초장 | 217.124423 | 1895.705919 | 88.55 |
각 항목마다 거리, 최대거리, 생육유사도가 측정되어 저장되었다.
결과를 살펴보면 거리가 작을수록 생육유사도가 크다.
즉, 농촌진흥청 생육 데이터와 비교 농가와의 DTW 거리가 차이가 안 난다는 뜻이다.
관부 직경의 경우 49.18%로 생육 유사도가 작지만 다른 항목들이 농촌진흥청 데이터와 유사하므로 크게 걱정할 필요는 없어 보인다.
✔ 생육 유사도 시각화
표로만 봐서는 사실 한눈에 보기 어렵고 와닿기 어려울 수 있다.
간단하게 시각화해서 각 항목별로 수치가 얼마나 되는지 보겠다.
hangmoks = dtw_calc['항목'].tolist()
similarities = dtw_calc['생육유사도'].tolist()
angles = np.linspace(0, 2 * np.pi, len(hangmoks), endpoint=False).tolist()
similarities += similarities[:1]
angles += angles[:1]
fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
ax.plot(angles, similarities, color='tab:blue', linewidth=2)
ax.fill(angles, similarities, color='tab:blue', alpha=0.25)
for angle, sim in zip(angles, similarities):
ax.text(angle, sim + 3, f'{sim:.1f}%', ha='center', va='center', fontsize=10)
ax.set_yticks([20, 40, 60, 80, 100])
ax.set_yticklabels(['20%', '40%', '60%', '80%', '100%'])
ax.set_xticks(angles[:-1])
ax.set_xticklabels(hangmoks, fontproperties="Malgun Gothic")
plt.tight_layout()
plt.show()
👉 위의 코드를 실행하면 차트가 나오게 된다. 필자가 원하는 시각화가 나왔다.
👉 이 시각화는 각 항목별로 얼마나 생육 패턴이 유사한지를 한눈에 보여준다.
👉 유사도가 높을수록 농촌진흥청 기준 데이터와 비슷하게 관리되었다는 뜻이 된다.
지금까지 필자는 스마트팜 생육 데이터를 기반으로 DTW(Dynamic Time Warping) 알고리즘을 활용해 농가 생육 데이터와 농촌진흥청 데이터를 비교하고, 생육 유사도를 수치화해 보았다.
이 과정은 다음과 같은 상황에서 유용하게 활용될 수 있다:
- 우리 농가의 생육 데이터가 "표준 생육 패턴"과 얼마나 유사한지 평가하고 싶을 때
- 새로운 재배 전략을 도입했을 때, 생육 패턴이 이전보다 좋아졌는지를 정량적으로 판단하고 싶을 때
- 여러 농가 간 유사도를 분석해, 유사한 생육 전략을 그룹핑하거나 벤치마킹하고 싶을 때
👉 추후에 개발까지 하여 파일을 업로드하면 자동으로 생육유사도가 나올 수 있게 개발을 할 예정이다.
📌 스마트팜코리아 데이터로 생육 유사도 측정 시리즈
👉이전편
2025.04.20 - [Analysis] - [SmartFarm] 스마트팜코리아 데이터로 생육 유사도 측정 (기획편)
2025.04.25 - [Analysis] - [SmartFarm] 스마트팜코리아 데이터로 생육 유사도 측정 (수집&처리편)
👉다음편
2025.06.12 - [Analysis] - [SmartFarm] 스마트팜코리아 데이터로 생육 유사도 측정(배포편)
'Analysis' 카테고리의 다른 글
[SmartFarm] 스마트팜코리아 데이터로 생육 유사도 측정(배포편) (0) | 2025.06.12 |
---|---|
[SmartFarm] 스마트팜코리아 데이터로 생육 유사도 측정 (수집&처리편) (0) | 2025.04.25 |
[SmartFarm] 스마트팜코리아 데이터로 생육 유사도 측정 (기획편) (0) | 2025.04.20 |