도매시장 경락 데이터의 이해-배추(1/3)

도매시장 경락 데이터의 이해-배추(1/3)

서울가락도매시장 30년 전 풍경, Thumbnail 이미지 출처


도입 파트 작성 예정

이번 Post에서 사용하는 데이터는 농림축산식품교육문화정보원에서 수집하여 공공데이터포털을 통해 제공되는 농수축산물 도매시장 상세 경락가격 Open API에서 수집한 데이터로 AWS S3 Agdata Lab 저장소에서 다운로드할 수 있다. 수집 기간은 2002년 1월부터 2018년 4월까지이며 전국 34개의 농산물 공영도매시장에서 거래된 배추 품목의 모든 데이터가 기록되어 있다.

먼저 csv파일을 읽어서 dat 데이터로 저장하고 측정치 개수를 확인해보자. nrow()로 확인해본 결과 6,025,998개이다. 총 196개월 중 월 20일 도매시장이 개장되었다고 가정했을 때 하루 평균 거래 건수는 1537.2건으로 단순화해서 볼 수 있겠으나 배추가 출하되는 계절에는 훨씬 많은 거래가 일어난다.

  library(plotly); library(dplyr); library(tidyr) ;library(ggplot2); library(gridExtra); library(knitr); library(kableExtra)
dat <- read.csv('cabbage_0201-1804_raw.csv')
print(paste('number of observation :',nrow(dat)))
## [1] "number of observation : 6025998"

날짜별 거래건수를 합해서 내림차순으로 정렬해서 상위 거래 건수를 확인해보면 거래가 많을 때는 3500건 이상 있었던 것을 확인할 수 있다.

dat %>% group_by(date) %>% tally() %>% arrange(desc(n)) %>% head()
## # A tibble: 6 x 2
##       date     n
##      <int> <int>
## 1 20131202  4103
## 2 20131118  3669
## 3 20171124  3597
## 4 20101122  3589
## 5 20121203  3579
## 6 20171117  3567

어떤 변수들로 구성되었는지 확인해보자.

names(dat)
##  [1] "aucCodeName" "catname"     "date"        "grade"       "insname"    
##  [6] "market"      "package"     "price"       "prodname"    "prut"       
## [11] "qty"         "sanji"       "shipment"    "size"        "spename"    
## [16] "unit"

각각의 의미는 다음과 같다

  catcode(중분류코드),  catname(중분류명),  date(경매날짜),  grade(등급),  insname(도매법인명),  market(도매시장명),  price(거래가격),  prodname(품목명),  prut(거래단량),  qty(거래량), sanji(출하지역), size(크기명),  spename(품종명),  unit(단위명)

dat 데이터의 1~30행 데이터를 살펴보면 다음과 같다.

Table 1: 도매시장 경매 raw 데이터 예시
aucCodeName catname date grade insname market package price prodname prut qty sanji shipment size spename unit
경매 엽경채류 20020102 무등급 농협북대구(공) 대구북부도매시장 기타 4700 배추 10 65 대구광역시 북구 3 기타 기타 kg
경매 엽경채류 20020102 무등급 대인농산 인천구월도매시장 트럭 1200000 배추 5 1 인천광역시 계양구 3 기타 월동배추 ton
경매 엽경채류 20020102 인천농산물 인천구월도매시장 트럭 1460000 배추 6500 1 전라남도 영암군 3 기타 기타 kg
경매 엽경채류 20020102 인천농산물 인천구월도매시장 그물망 2200 배추 10 790 전라남도 영광군 3 기타 기타 kg
경매 엽경채류 20020102 4등 인천농산물 인천구월도매시장 그물망 1100 배추 10 10 전라남도 영광군 3 기타 기타 kg
경매 엽경채류 20020102 인천농산물 인천구월도매시장 그물망 2300 배추 10 750 전라남도 영광군 3 기타 기타 kg
경매 엽경채류 20020102 보통 인천농산물 인천구월도매시장 그물망 1200 배추 10 20 전라남도 영광군 3 기타 기타 kg
경매 엽경채류 20020102 구월원협(공) 인천구월도매시장 상자 8000 배추 20 10 전라남도 무안군 2 기타 봄배추 kg
경매 엽경채류 20020102 구월원협(공) 인천구월도매시장 상자 8000 배추 20 5 전라남도 무안군 2 기타 봄배추 kg
경매 엽경채류 20020102 구월원협(공) 인천구월도매시장 상자 8000 배추 20 5 전라남도 무안군 2 기타 봄배추 kg
경매 엽경채류 20020102 구월원협(공) 인천구월도매시장 상자 8000 배추 20 5 전라남도 무안군 2 기타 봄배추 kg
경매 엽경채류 20020102 구월원협(공) 인천구월도매시장 상자 8000 배추 20 4 전라남도 무안군 2 기타 봄배추 kg
경매 엽경채류 20020102 덕풍청과 인천구월도매시장 트럭 1600000 배추 5000 1 전라남도 진도군 4 기타 기타 kg
경매 엽경채류 20020102 덕풍청과 인천구월도매시장 트럭 1400000 배추 5000 1 전라남도 해남군 3 기타 기타 kg
경매 엽경채류 20020102 덕풍청과 인천구월도매시장 트럭 1600000 배추 5000 1 전라남도 해남군 3 기타 기타 kg
경매 엽경채류 20020102 경인농산 인천삼산도매시장 1200 배추 2 60 a 2 기타 기타 kg
경매 엽경채류 20020102 경인농산 인천삼산도매시장 1200 배추 2 40 a 2 기타 기타 kg
경매 엽경채류 20020102 경인농산 인천삼산도매시장 트럭 1400000 배추 6500 1 강원도 원주시 3 기타 기타 kg
경매 엽경채류 20020102 경인농산 인천삼산도매시장 트럭 1500000 배추 6500 1 전라남도 해남군 3 기타 기타 kg
경매 엽경채류 20020102 삼산원협(공) 인천삼산도매시장 트럭 1000000 배추 5 1 전라남도 장흥군 3 기타 기타 ton
a 엽경채류 20020102 광주청과 광주각화도매시장 420000 배추 1000 1 광주광역시 동구 4 기타 기타 kg
a 엽경채류 20020102 광주청과 광주각화도매시장 300000 배추 1000 1 전라남도 해남군 2 기타 기타 kg
a 엽경채류 20020102 무등급 광주중앙청과 광주각화도매시장 상자 2500 배추 2 5 광주광역시 동구 3 기타 기타 kg
a 엽경채류 20020102 무등급 광주중앙청과 광주각화도매시장 상자 2500 배추 2 5 광주광역시 동구 3 기타 기타 kg
a 엽경채류 20020102 무등급 광주중앙청과 광주각화도매시장 상자 2500 배추 2 5 광주광역시 동구 3 기타 기타 kg
a 엽경채류 20020102 무등급 광주중앙청과 광주각화도매시장 상자 2500 배추 2 5 광주광역시 동구 3 기타 기타 kg
a 엽경채류 20020102 무등급 광주중앙청과 광주각화도매시장 상자 2500 배추 2 5 광주광역시 동구 3 기타 기타 kg
a 엽경채류 20020102 무등급 광주중앙청과 광주각화도매시장 상자 2500 배추 2 5 광주광역시 동구 3 기타 기타 kg
a 엽경채류 20020102 무등급 광주중앙청과 광주각화도매시장 상자 2500 배추 2 5 광주광역시 동구 3 기타 기타 kg
a 엽경채류 20020102 무등급 광주중앙청과 광주각화도매시장 상자 2500 배추 2 5 광주광역시 동구 3 기타 기타 kg

이제 row 데이터가 어떤 형태인지 파악이 되었다면 추가로 필요한 변수들을 생성해보자. 먼저 yyyymmdd 형태로 되어 있는 date 변수는 날짜에 대한 정보로만 사용 가능하기 때문에 연도, 월, 일을 분리해서 각각의 의미를 가지도록 만들어줘야 한다. 아래 코드로 substr() 함수를 이용해서 year, month, day 변수를 만들자. 그 다음은 date로부터 week(주차)와 wday(요일) 정보를 추출한다.

dat <- dat %>% mutate(year = as.numeric(substr(date, 1, 4)), month = as.numeric(substr(date, 
    5, 6)), day = as.numeric(substr(date, 7, 8)), week = as.numeric(format(as.Date(as.character(date), 
    format = "%Y%m%d"), "%W")) + 1, wday = weekdays(as.Date(as.character(date), 
    format = "%Y%m%d")))

데이터를 살펴보면 한번의 거래는 prut unit 만큼을 price의 가격으로 qty개가 거래되는 것을 확인할 수 있다. 4번째 행을 보면 10 kg 만큼을 1100원의 가격으로 10개 거래한다고 하면 10kg 포장단위 1개가 1100원이고 10개 거래했으니 11,000원치의 거래가 일어난 것이다. 1번째 행을 보면 5톤 포장단위 하나가 1,200,000원이고 1개를 거래했으니 120만원치 거래가 일어난 것이다. 즉 price 변수는 단위 가격이 아니기 때문에 전체 거래금액에서 거래중량을 나누어서 단위가격 변수를 만들어줘야 한다. 도매시장 데이터에서는 중량단위가 g, kg, ton의 3가지가 사용되기 때문에 각각의 단위을 kg 단위로 통일해서 거래중량을 의미하는 weight 변수를 만들고, priceqty을 곱해서 거래금액을 의매하는 sales_amt 변수를 만들었다. sales_amtweight로 나눠서 price_per_kg 변수를 만들자 이제 이 변수는 kg당 가격이 된다.

sanji 변수는 광역단체 시군 형태의 정보가 입력되어야 하는데 광역단체만 입력된 경우가 많아서 광역단체를 의미하는 sanji_wide 변수와 시군 정보를 의미하는 sanji_city로 분리해서 변수를 만들었다. sanji_wide2 변수는 세종시가 광역단위로는 규모가 작아서 충청남도를 입력하도록 한 변수이다.

언제나 그렇듯 row 데이터는 날 것 그대로의 데이터이기 때문에 이상치가 존재하는지 확인하는 과정을 반드시 거쳐야 한다. price_per_kg 변수를 이용해서 이상치를 살펴보자. 먼저 histogram을 이용한 분포를 보면 전체 데이터의 histogram은 최대 kg당 100만원 가까운 배추가 거래된 것을 확인할 수 있다. 금으로 만들어진 배추는 아닐텐데..

그래서 다시 kg당 가격이 5000원 미만으로 필터링하여 histogram을 그려보면 실제 거래가 가장 많이 일어나고 있는 가격 범위는 1000원 이하인 것을 확인할 수 있다.

gh1 <- ggplot(dat, aes(x = price_per_kg)) + geom_histogram(fill = "red", color = "red", bins = 1000)  
gh2 <- ggplot(dat %>% filter(price_per_kg<=5000), aes(x = price_per_kg)) + geom_histogram(fill = "firebrick2", color = "firebrick2", bins = 1000) 
grid.arrange(gh1, gh2, ncol=1, nrow=2)

kg당 100만원짜리 금으로 만든 배추의 거래 기록을 살펴보기 위해 price_per_kg을 내림차순으로 정령하여 상위 30개만 추려서 보면 아래 표와 같다. 대부분 단위가 g이다. 10g, 12g씩 거래가 되고 있다. 배추 10g이면 배추잎 하나를 뜯어서 파는것인가? 그렇지 않을 것이다. 단위가 kg이 들어가야 하는데 잘못들어갔을 가능성이 높다. 이런 데이터는 gkg으로 보정하는 것도 방법이 될 수 있다. 1000을 나눠주면 100만원은 천원이 되니 합리적인 범위에 들어간다. 그러나 어느 범위까지 gkg으로 바꿔줄것인지에 대한 명확한 기준을 세우기가 어렵다면 보정하기보다 특정 범위삭제하는 것이 나을수도 있다.

Table 2: kg당 가격 기준 내림차순 데이터 상위 30개
aucCodeName catname date grade insname market package price prodname prut qty sanji sanji_wide sanji_city shipment size spename unit year month day week wday weight sales_amt price_per_kg sanji_wide2
경매 엽경채류 20130822 무등급 강서청과 서울강서도매시장 11800 배추 12.00 645 경상북도 영천시 경상북도 영천시 3 1 기타 g 2013 8 22 34 목요일 7.74 7611000 983333.3 경상북도
경매 엽경채류 20130910 무등급 강서청과 서울강서도매시장 11800 배추 12.00 720 경기도 여주시 경기도 여주시 3 1 기타 g 2013 9 10 37 화요일 8.64 8496000 983333.3 경기도
경매 엽경채류 20130321 무등급 강서청과 서울강서도매시장 11700 배추 12.00 -750 강원도 평창군 강원도 평창군 3 1 월동배추 g 2013 3 21 12 목요일 -9.00 -8775000 975000.0 강원도
경매 엽경채류 20130321 무등급 강서청과 서울강서도매시장 11700 배추 12.00 700 강원도 평창군 강원도 평창군 3 1 월동배추 g 2013 3 21 12 목요일 8.40 8190000 975000.0 강원도
경매 엽경채류 20130903 무등급 강서청과 서울강서도매시장 11700 배추 12.00 580 광주광역시 동구 광주광역시 동구 3 1 기타 g 2013 9 3 36 화요일 6.96 6786000 975000.0 광주광역시
경매 엽경채류 20130828 무등급 강서청과 서울강서도매시장 11600 배추 12.00 800 광주광역시 서구 광주광역시 서구 3 1 기타 g 2013 8 28 35 수요일 9.60 9280000 966666.7 광주광역시
경매 엽경채류 20030505 무등급 광주원협(공) 광주각화도매시장 기타 960000 배추 1.00 1 전라남도 무안군 전라남도 무안군 2 기타 봄배추 kg 2003 5 5 19 월요일 1.00 960000 960000.0 전라남도
경매 엽경채류 20051031 호남청과 광주서부도매시장 상자 480000 배추 0.50 1 광주광역시 광산구 광주광역시 광산구 3 기타 기타 kg 2005 10 31 45 월요일 0.50 480000 960000.0 광주광역시
경매 엽경채류 20110729 무등급 강서청과 서울강서도매시장 9600 배추 10.00 700 부산광역시 부산진구 부산광역시 부산진구 3 1 기타 g 2011 7 29 31 금요일 7.00 6720000 960000.0 부산광역시
경매 엽경채류 20110823 무등급 강서청과 서울강서도매시장 9600 배추 10.00 750 부산광역시 수영구 부산광역시 수영구 3 1 고냉지배추 g 2011 8 23 35 화요일 7.50 7200000 960000.0 부산광역시
경매 엽경채류 20120412 무등급 강서청과 서울강서도매시장 9600 배추 10.00 700 경상남도 창녕군 경상남도 창녕군 3 1 월동배추 g 2012 4 12 16 목요일 7.00 6720000 960000.0 경상남도
경매 엽경채류 20120427 무등급 강서청과 서울강서도매시장 9600 배추 10.00 600 충청북도 청주시 충청북도 청주시 3 1 봄배추 g 2012 4 27 18 금요일 6.00 5760000 960000.0 충청북도
정가수의 엽경채류 20160213 농협반여(공) 부산반여도매시장 기타 48000 배추 0.05 1 경상남도 창원시 경상남도 창원시 2 기타 기타 kg 2016 2 13 7 토요일 0.05 48000 960000.0 경상남도
정가수의 엽경채류 20171016 농협반여(공) 부산반여도매시장 기타 48000 배추 0.05 1 경상남도 창원시 경상남도 창원시 2 기타 기타 kg 2017 10 16 43 월요일 0.05 48000 960000.0 경상남도
정가수의 엽경채류 20171208 농협반여(공) 부산반여도매시장 기타 48000 배추 0.05 1 경상남도 창원시 경상남도 창원시 2 기타 기타 kg 2017 12 8 50 금요일 0.05 48000 960000.0 경상남도
정가수의 엽경채류 20171208 농협반여(공) 부산반여도매시장 기타 48000 배추 0.05 1 경상남도 창원시 경상남도 창원시 2 기타 기타 kg 2017 12 8 50 금요일 0.05 48000 960000.0 경상남도
경매 엽경채류 20120829 무등급 강서청과 서울강서도매시장 11500 배추 12.00 525 강원도 동해시 강원도 동해시 3 1 고냉지배추 g 2012 8 29 36 수요일 6.30 6037500 958333.3 강원도
경매 엽경채류 20120926 무등급 강서청과 서울강서도매시장 11500 배추 12.00 520 서울특별시 강서구 서울특별시 강서구 3 1 고냉지배추 g 2012 9 26 40 수요일 6.24 5980000 958333.3 서울특별시
경매 엽경채류 20130425 무등급 강서청과 서울강서도매시장 11500 배추 12.00 780 광주광역시 광산구 광주광역시 광산구 3 1 월동배추 g 2013 4 25 17 목요일 9.36 8970000 958333.3 광주광역시
경매 엽경채류 20130812 무등급 강서청과 서울강서도매시장 11500 배추 12.00 600 경기도 여주시 경기도 여주시 3 1 기타 g 2013 8 12 33 월요일 7.20 6900000 958333.3 경기도
경매 엽경채류 20130902 무등급 강서청과 서울강서도매시장 11500 배추 12.00 670 경상북도 영천시 경상북도 영천시 3 1 기타 g 2013 9 2 36 월요일 8.04 7705000 958333.3 경상북도
경매 엽경채류 20040917 무등급 광주중앙청과 광주각화도매시장 기타 3800000 배추 4.00 1 전라남도 나주시 전라남도 나주시 2 기타 고냉지배추 kg 2004 9 17 38 금요일 4.00 3800000 950000.0 전라남도
경매 엽경채류 20110820 무등급 강서청과 서울강서도매시장 9500 배추 10.00 650 경상북도 예천군 경상북도 예천군 3 1 고냉지배추 g 2011 8 20 34 토요일 6.50 6175000 950000.0 경상북도
경매 엽경채류 20110823 무등급 강서청과 서울강서도매시장 9500 배추 10.00 760 강원도 평창군 강원도 평창군 3 1 저장배추 g 2011 8 23 35 화요일 7.60 7220000 950000.0 강원도
정가수의 엽경채류 20171003 천안농협(공) 천안도매시장 상자 950000 배추 1.00 1 서울특별시 서울특별시 NA 3 기타 기타 kg 2017 10 3 41 화요일 1.00 950000 950000.0 서울특별시
정가수의 엽경채류 20160718 충북원협(청주) 청주도매시장 상자 9495 배추 0.01 1 서울특별시 서울특별시 NA 1 기타 기타 kg 2016 7 18 30 월요일 0.01 9495 949500.0 서울특별시
정가수의 엽경채류 20161021 농협안산(공) 안산도매시장 기타 18846 배추 0.02 1 서울특별시 서울특별시 NA 3 기타 기타 kg 2016 10 21 43 금요일 0.02 18846 942300.0 서울특별시
경매 엽경채류 20130904 무등급 강서청과 서울강서도매시장 11300 배추 12.00 720 경상북도 영천시 경상북도 영천시 3 1 기타 g 2013 9 4 36 수요일 8.64 8136000 941666.7 경상북도
경매 엽경채류 20121124 무등급 강서청과 서울강서도매시장 11200 배추 12.00 650 강원도 평창군 강원도 평창군 3 1 김장(가을)배추 g 2012 11 24 48 토요일 7.80 7280000 933333.3 강원도
경매 엽경채류 20130826 무등급 강서청과 서울강서도매시장 11200 배추 12.00 670 경기도 여주시 경기도 여주시 3 1 기타 g 2013 8 26 35 월요일 8.04 7504000 933333.3 경기도

row 데이터에서 단위가 g인 데이터만 필터링해서 단위가격이 낮은순에서 높은 순으로 정렬하고 순번을 매기기 위해 seq() 함수를 이용하여 SEQ 변수를 만들었다. SEQ 변수를 x축으로 하고 price_per_kg 변수를 y축으로 하는 선그래프를 그려보면 360번째 순위에서 25,000원 정도 지점을 지나면 급격하게 증가하는 패턴을 보인다. 뭔가 정상적이지 않는 것을 감지할 수 있다.

데이터를 들여다보면 100g 이하의 거래단량을 가진 거래들에서 지나치게 높은 price_per_kg 값을 확인할 수 있었다. 그래서 다시 100g 이하의 거래들만 필터링해서 정렬 데이터의 선그래프를 그려보면 편평한 지점들이 있고 15,000원 정도의 가격에서의 거래량도 7건 정도 있는 것으로 확인된다. 그래도 최대 25,000원에서 그친다.

d1 <- dat %>% filter(unit=='g')
g1 <- ggplot(d1 %>% arrange(price_per_kg) %>% mutate(SEQ=seq(1,nrow(d1),1))) + geom_line(aes(x=SEQ,y=price_per_kg), linetype='solid', size=0.5, alpha=0.8, color='blue') 
d2 <- d1 %>% filter(prut>=100)
g2 <- ggplot(d2 %>% arrange(price_per_kg) %>% mutate(SEQ=seq(1,nrow(d2),1))) + geom_line(aes(x=SEQ,y=price_per_kg), linetype='solid', size=0.5, alpha=0.8, color='blue') 
subplot(ggplotly(g1),ggplotly(g2), margin = 0.05, nrows=2) 

100g 미만의 거래는 제거하여 순도를 약간 높인 dat2 데이터를 만들었다.

dat2 <- dat %>% filter(!((unit=='g' & prut<100) | (unit=='kg' & prut<0.1)))

다시 kg당 가격이 10,000원 이상인 케이스들로 정렬 데이터의 선그래프를 그려보자(좌편-상단). 이상치를 좀 제거했나 싶었는데 아직 갈 길이 멀다. 누적 선그래프의 612만~614만, 613만~615만, 614만~616만 SEQ의 패턴을 살펴보면 계단식으로 편평한 지점들이 군데군데 보이는 것으로 보아 거래가 빈번히 발생했던 특정 가격대가 있는 것을 확인할 수 있다. 우편-상단 -> 좌편-하단 -> 우편-하단 그래프 순으로 살펴보면 price_per_kg이 커질수록 거래량은 적어지는 것을 확인할 수 있다. 단순히 그래프의 패턴 만으로는 얼마 이상의 price_per_kr 부터 분석에서 제외시켜도 좋을지 판단하기는 어렵다. 결국 해답은 현장에서 찾아야 할 것이다.

d3 <- dat2 %>% arrange(price_per_kg) %>% mutate(SEQ=seq(1,nrow(dat2),1)) %>% filter(price_per_kg >= 10000)
g3 <- ggplot(d3) + geom_line(aes(x=SEQ,y=price_per_kg), linetype='solid', size=0.5, alpha=0.8, color='red') 
d3a <- dat2 %>% arrange(price_per_kg) %>% mutate(SEQ=seq(1,nrow(dat2),1)) %>% filter(SEQ > 5950000, SEQ < 5970000) 
d3b <- dat2 %>% arrange(price_per_kg) %>% mutate(SEQ=seq(1,nrow(dat2),1)) %>% filter(SEQ > 5960000, SEQ < 5980000)  
d3c <- dat2 %>% arrange(price_per_kg) %>% mutate(SEQ=seq(1,nrow(dat2),1)) %>% filter(SEQ > 5970000, SEQ < 5990000) 
g3a <- ggplot(d3a) + geom_line(aes(x=SEQ,y=price_per_kg), linetype='solid', size=0.5, alpha=0.8, color='red1')
g3b <- ggplot(d3b) + geom_line(aes(x=SEQ,y=price_per_kg), linetype='solid', size=0.5, alpha=0.8, color='red2') 
g3c <- ggplot(d3c) + geom_line(aes(x=SEQ,y=price_per_kg), linetype='solid', size=0.5, alpha=0.8, color='red3') 
subplot(ggplotly(g3),ggplotly(g3a), ggplotly(g3b), ggplotly(g3c), margin = 0.05, nrows=2)