농업/AgTech 데이터 분석¶
스마트팜과 정밀농업 시대의 데이터 분석 가이드. 기상, 토양, 위성, IoT 센서 데이터를 활용한 수확량 예측, 병충해 탐지, 관개 최적화 등 농업 전반의 데이터 기반 의사결정을 다룬다.
개요¶
정밀농업(Precision Agriculture)은 데이터를 기반으로 농업 투입물(물, 비료, 농약)을 최적화하고 수확량을 극대화하는 접근이다. 센서, 위성, 드론 기술의 발전으로 농업 데이터의 양과 질이 급격히 향상되었다.
농업 데이터 분석의 가치¶
| 영역 | 기존 방식 | 데이터 기반 방식 | 개선 효과 |
|---|---|---|---|
| 관개 | 일정 기반 살수 | 토양 수분 센서 기반 | 물 사용량 20-30% 절감 |
| 비료 | 균일 살포 | 구역별 가변 살포 | 비료 15-20% 절감 |
| 병충해 | 예방 살포 | 조기 탐지 후 선택적 처리 | 농약 30-50% 절감 |
| 수확 | 경험 기반 시기 결정 | 예측 모델 기반 최적 시기 | 수확량 5-15% 증가 |
| 가격 | 시장 관행 | 시계열 예측 기반 출하 전략 | 수익 10-20% 증가 |
핵심 데이터 소스¶
데이터 유형별 정리¶
| 데이터 유형 | 소스 | 수집 주기 | 형식 | 활용 |
|---|---|---|---|---|
| 기상 | 기상청 API, 자체 기상대 | 시간별 | 시계열 | 수확량 예측, 병충해 |
| 토양 | IoT 센서, 정밀 토양 분석 | 분 단위 / 연 1회 | 센서값, 성분표 | 관개, 비료 |
| 위성 영상 | Sentinel-2, Landsat | 5-16일 | GeoTIFF | NDVI, 생육 모니터링 |
| 드론 영상 | RGB, 멀티스펙트럴 | 주 1회 | 정사영상 | 병충해, 생육 상태 |
| 생육 데이터 | 수동 조사, 카메라 | 주 1회 | 정형 | 성장 단계, 수확 시기 |
| 시장 데이터 | aT, KAMIS | 일별 | 시계열 | 가격 예측, 출하 전략 |
NDVI (Normalized Difference Vegetation Index)¶
식생의 건강 상태를 나타내는 핵심 지표:
NDVI = (NIR - Red) / (NIR + Red)
값 범위:
-1.0 ~ 0.0 : 물, 구름, 눈
0.0 ~ 0.2 : 나지, 건물
0.2 ~ 0.4 : 초기 생육 / 수확 후
0.4 ~ 0.6 : 중간 생육
0.6 ~ 0.9 : 건강한 식생 (최성기)
한국 농업 공공데이터¶
| 데이터 | 제공처 | 접근 방법 | 내용 |
|---|---|---|---|
| 농업관측데이터 | 한국농촌경제연구원 | API | 작물별 재배면적, 생산량 |
| 농산물유통정보 | aT (KAMIS) | API | 일별 도매/소매 가격 |
| 기상관측 | 기상청 | API | 기온, 강수, 일사량 |
| 토양환경정보 | 농촌진흥청 | 웹 다운로드 | 토양 성분, pH, 유기물 |
| 병충해 발생정보 | 농촌진흥청 | API | 병충해 발생 현황 |
| 농업경영체 등록 | 농림축산식품부 | 공공데이터포털 | 경작 면적, 작물 |
| 통계 | KOSIS | API/웹 | 농업 총조사, 생산비 |
분석 영역별 가이드¶
1. 수확량 예측¶
작물별 수확량을 예측하여 생산 계획과 유통을 최적화한다.
입력 변수:
| 변수 그룹 | 변수 | 중요도 |
|---|---|---|
| 기상 | 적산 온도, 강수량, 일사량, 일교차 | 높음 |
| 토양 | pH, 유기물, 질소, 인산, 칼리 | 높음 |
| 생육 | NDVI 시계열, 생육 단계, 개화일 | 높음 |
| 재배 | 품종, 파종일, 재식 밀도 | 중간 |
| 이력 | 전년도 수확량, 윤작 이력 | 중간 |
모델링 접근:
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import TimeSeriesSplit
def build_yield_model(data: pd.DataFrame) -> dict:
"""수확량 예측 모델 학습
Args:
data: columns = [year, region, crop, gdd, precip,
ndvi_max, soil_ph, ..., yield_kg_ha]
"""
feature_cols = [
'gdd', # 생육도일 (Growing Degree Days)
'precip_total', # 총 강수량
'precip_cv', # 강수 변동계수
'solar_rad', # 일사량
'ndvi_max', # 최대 NDVI
'ndvi_integral', # NDVI 적분값
'soil_ph',
'soil_om', # 유기물 함량
'planting_doy', # 파종 일수 (day of year)
]
X = data[feature_cols]
y = data['yield_kg_ha']
# 시계열 교차검증
tscv = TimeSeriesSplit(n_splits=5)
scores = []
model = GradientBoostingRegressor(
n_estimators=500,
max_depth=5,
learning_rate=0.05,
subsample=0.8,
)
for train_idx, test_idx in tscv.split(X):
model.fit(X.iloc[train_idx], y.iloc[train_idx])
score = model.score(X.iloc[test_idx], y.iloc[test_idx])
scores.append(score)
model.fit(X, y)
return {
"model": model,
"cv_r2": np.mean(scores),
"feature_importance": dict(zip(feature_cols,
model.feature_importances_)),
}
2. 병충해 탐지¶
이미지 분류와 시계열 분석을 결합하여 병충해를 조기에 탐지한다.
탐지 파이프라인:
┌──────────────────┐
│ 데이터 수집 │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 드론 영상 │ │ NDVI 변화 │ │ 기상 조건 │
│ (RGB/MSI) │ │ (시계열) │ │ (온습도) │
└──────┬─────┘ └──────┬─────┘ └──────┬─────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ CNN 분류 │ │ 이상 탐지 │ │ 위험 지수 │
│ (병징 감지)│ │ (급격 변화)│ │ (발생 확률)│
└──────┬─────┘ └──────┬─────┘ └──────┬─────┘
│ │ │
└──────────────┼──────────────┘
▼
┌──────────────────┐
│ 종합 판단 + 알림 │
└──────────────────┘
기상 조건 기반 병충해 위험 지수:
def calculate_disease_risk(weather_df: pd.DataFrame) -> pd.Series:
"""기상 조건 기반 병해 위험 지수 (0-100)
주요 인자: 온도, 습도, 이슬점, 강수
"""
risk = pd.Series(0.0, index=weather_df.index)
# 온도 적합성 (병원균 활동 최적 온도)
temp = weather_df['temp_avg']
temp_risk = np.where(
(temp >= 15) & (temp <= 25),
30, # 최적
np.where((temp >= 10) & (temp <= 30), 15, 0)
)
# 습도 위험 (높은 습도 = 높은 위험)
humidity = weather_df['humidity_avg']
humid_risk = np.where(humidity >= 85, 30,
np.where(humidity >= 70, 15, 0))
# 연속 습윤 시간
wet_hours = weather_df['wet_duration_hours']
wet_risk = np.clip(wet_hours * 2, 0, 30)
# 최근 강수
precip = weather_df['precip_3day']
precip_risk = np.where(precip > 30, 10,
np.where(precip > 10, 5, 0))
risk = temp_risk + humid_risk + wet_risk + precip_risk
return np.clip(risk, 0, 100)
3. 관개 최적화¶
토양 수분 센서와 기상 데이터를 결합하여 최적 관개 시점과 양을 결정한다.
수분 수지 모델:
토양 수분(t+1) = 토양 수분(t) + 강수 + 관개 - ET - 배수
ET (증발산) = ET0 x Kc
ET0: 기준 증발산 (Penman-Monteith)
Kc: 작물 계수 (생육 단계별 상이)
관개 결정 로직:
def irrigation_decision(
soil_moisture: float, # 현재 토양 수분 (%)
field_capacity: float, # 포장 용수량 (%)
wilting_point: float, # 위조점 (%)
crop_kc: float, # 작물 계수
et0_forecast: list, # 향후 3일 ET0 예측
rain_forecast: list, # 향후 3일 강수 예측
) -> dict:
"""관개 의사결정"""
# 가용 수분
available = (soil_moisture - wilting_point) / (field_capacity - wilting_point)
# 향후 3일 수분 소모 예측
et_3day = sum(et0 * crop_kc for et0 in et0_forecast)
rain_3day = sum(rain_forecast)
net_loss = et_3day - rain_3day
# 관개 필요량 계산
if available < 0.4: # 긴급 (MAD 초과)
amount = (field_capacity - soil_moisture) * soil_depth * 10 # mm
return {"action": "irrigate_now", "amount_mm": amount,
"priority": "high"}
elif available < 0.6 and net_loss > 0:
amount = net_loss * 1.1 # 10% 여유
return {"action": "schedule", "amount_mm": amount,
"priority": "medium", "within_hours": 24}
else:
return {"action": "wait", "next_check_hours": 12,
"priority": "low"}
4. 가격 예측¶
농산물 가격을 시계열 모델로 예측하여 출하 전략을 수립한다.
가격 예측 SQL (KAMIS 데이터 기반):
-- 농산물 일별 가격 시계열 조회
SELECT
p.date,
p.item_name,
p.market_name,
p.avg_price,
-- 이동평균
AVG(p.avg_price) OVER (
PARTITION BY p.item_code
ORDER BY p.date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS ma_7d,
AVG(p.avg_price) OVER (
PARTITION BY p.item_code
ORDER BY p.date
ROWS BETWEEN 29 PRECEDING AND CURRENT ROW
) AS ma_30d,
-- 전년 동기 대비
LAG(p.avg_price, 365) OVER (
PARTITION BY p.item_code
ORDER BY p.date
) AS price_yoy,
-- 변동률
(p.avg_price - LAG(p.avg_price, 1) OVER (
PARTITION BY p.item_code ORDER BY p.date
)) / NULLIF(LAG(p.avg_price, 1) OVER (
PARTITION BY p.item_code ORDER BY p.date
), 0) * 100 AS daily_change_pct
FROM prices p
WHERE p.item_code = '100' -- 배추
AND p.date >= DATE_SUB(CURRENT_DATE, INTERVAL 2 YEAR)
ORDER BY p.date;
-- 월별 계절성 분석
SELECT
MONTH(date) AS month,
item_name,
AVG(avg_price) AS avg_price,
STDDEV(avg_price) AS std_price,
MIN(avg_price) AS min_price,
MAX(avg_price) AS max_price,
COUNT(*) AS obs_count
FROM prices
WHERE item_code = '100'
AND date >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
GROUP BY MONTH(date), item_name
ORDER BY month;
5. 공급망 예측¶
생산-유통-소비를 연결하는 공급망 최적화.
| 예측 대상 | 입력 변수 | 모델 | 활용 |
|---|---|---|---|
| 생산량 | 재배면적, 기상, NDVI | GB, LSTM | 수급 균형 |
| 출하량 | 수확량, 가격, 재고 | 시계열 | 물류 계획 |
| 수요 | 인구, 계절, 가격 | ARIMA, Prophet | 가격 안정 |
| 폐기량 | 유통 단계, 온도 | 로지스틱 | 손실 최소화 |
KPI 체계¶
농장 수준 KPI¶
| KPI | 산식 | 단위 | 목표 |
|---|---|---|---|
| 단위면적 수확량 | 총 수확량 / 재배면적 | kg/ha | 작물별 상이 |
| 용수 효율 | 수확량 / 관개량 | kg/m3 | 최대화 |
| 비료 효율 | 수확량 / 비료 투입량 | kg/kg | 최대화 |
| 농약 사용량 | 농약 사용 / 재배면적 | kg/ha | 최소화 |
| 노동 생산성 | 수확량 / 노동 시간 | kg/h | 최대화 |
| 투입 대비 산출비 | 판매액 / 총 투입비용 | ratio | > 2.0 |
| 품질 등급 비율 | 상품 / 전체 | % | > 80% |
스마트팜 운영 KPI¶
| KPI | 설명 | 모니터링 주기 |
|---|---|---|
| 센서 가동률 | 정상 작동 센서 비율 | 실시간 |
| 데이터 수집률 | 예정 대비 실제 수집 비율 | 일별 |
| 알림 정확도 | 알림 중 실제 조치 필요 비율 | 주별 |
| 자동 제어 비율 | 자동 관개/환기 비율 | 월별 |
| 에너지 효율 | 에너지 사용량 / 수확량 | 월별 |
사례 연구¶
네덜란드 스마트팜¶
| 항목 | 내용 |
|---|---|
| 특징 | 유리온실, 완전 환경 제어 |
| 기술 | 센서 네트워크, AI 기반 환경 제어, 로봇 수확 |
| 성과 | 토마토 수확량 세계 최고 (80kg/m2), 물 사용 90% 절감 |
| 핵심 | WUR(바헤닝언 대학) 연구 기반, 데이터 공유 문화 |
한국 스마트팜 혁신밸리¶
| 항목 | 내용 |
|---|---|
| 위치 | 전북 김제, 경남 밀양, 전남 고흥, 경북 상주 |
| 목적 | 청년 농업인 육성, 스마트팜 기술 보급 |
| 기술 | IoT 센서, 자동 환경 제어, 데이터 수집 플랫폼 |
| 데이터 | 농촌진흥청 스마트팜 빅데이터 플랫폼 |
Python 분석 예시: NDVI 시계열 + 수확량 예측¶
import pandas as pd
import numpy as np
from prophet import Prophet
import rasterio
from shapely.geometry import box
def extract_ndvi_timeseries(tif_paths: list, aoi: box) -> pd.DataFrame:
"""위성 영상에서 관심 영역의 평균 NDVI 추출"""
records = []
for path in tif_paths:
with rasterio.open(path) as src:
# AOI 내 픽셀 추출
window = rasterio.windows.from_bounds(*aoi.bounds, src.transform)
nir = src.read(4, window=window) # Band 4: NIR
red = src.read(3, window=window) # Band 3: Red
# NDVI 계산
ndvi = (nir.astype(float) - red) / (nir + red + 1e-10)
ndvi = np.where((ndvi >= -1) & (ndvi <= 1), ndvi, np.nan)
records.append({
"date": extract_date_from_path(path),
"ndvi_mean": np.nanmean(ndvi),
"ndvi_std": np.nanstd(ndvi),
"ndvi_max": np.nanmax(ndvi),
"valid_pixel_ratio": np.sum(~np.isnan(ndvi)) / ndvi.size,
})
return pd.DataFrame(records).sort_values("date")
def forecast_ndvi(ndvi_ts: pd.DataFrame, periods: int = 30) -> pd.DataFrame:
"""NDVI 시계열 예측 (Prophet)"""
df = ndvi_ts.rename(columns={"date": "ds", "ndvi_mean": "y"})
model = Prophet(
yearly_seasonality=True,
weekly_seasonality=False,
daily_seasonality=False,
)
model.fit(df)
future = model.make_future_dataframe(periods=periods)
forecast = model.predict(future)
return forecast[["ds", "yhat", "yhat_lower", "yhat_upper"]]
참고 자료¶
| 자료 | 링크 |
|---|---|
| 농촌진흥청 스마트팜 | https://www.smartfarmkorea.net/ |
| KAMIS 농산물유통정보 | https://www.kamis.or.kr/ |
| Sentinel Hub | https://www.sentinel-hub.com/ |
| FAO GAEZ (농업 생태 구역) | https://gaez.fao.org/ |
| 한국농촌경제연구원 | https://www.krei.re.kr/ |
최종 업데이트: 2026-03-25