본문 바로가기
데이터분석/R

[R 데이터분석 with 샤이니] 교통 카드 데이터 분석 사례 02 - 기초 분석

by 버섯도리 2022. 7. 30.

> ## 4 기초 분석 1 : 노선별, 시간대별 이용량


> # Step 1 : 노선별, 시간대별 이용량 특성 분석

> ## 이용자가 몇 번 버스를 타고 어디에서 어디로 이동하였는지 알아내기
> # 개별 이동 데이터 불러오기
> load("./01_save/02_003_trip_chain.rdata")
> # 버스노선 정보 불러오기
> route_map <- read.csv("./SBJ_1910_001/routestationmapping.csv", fileEncoding = "UTF-8")
> head(route_map)
      구분  운수사명 운수사ID 이비노선ID 표준노선ID 노선명
1 경기시내 경원여객M  2805000  216000044   28050900  M6410
2 경기시내  강화운수  4100100  232000028   41001001      2
3 경기시내  강화운수  4100100  232000029   41001013     88
4 경기시내  강화운수  4100100  232000061   41001020   3000
5 경기시내  강화운수  4100100  232000067   41001024    388
6 경기시내  강화운수  4100100  232000087   41001027      3
> # 노선 ID와 버스 번호(노선명) 추출
> route_map <- route_map[, 5:6]
> # 개별 이동 데이터와 버스 번호(노선명) 결합
> route_sta <- merge(trip_chain, route_map, by.x = "버스노선ID1", by.y = "표준노선ID")
> str(route_sta)
'data.frame': 234639 obs. of  66 variables:
 $ 버스노선ID1     : num  41002003 41002003 41002003 41002003 41002003 ...
 $ 암호화카드번호  : num  900474615375 900109571235 900456269492 100073476942 900517750322 ...
...
 $ S_시군명        : chr  "광주시" "성남시" "성남시" "광주시" ...
 $ S_정류소명      : chr  "현대모닝사이드1차아파트.새마을입구.신현리" "성남아트센터.태원고교" "금광1.2동주민센터.중원어린이도서관" "뉴광주농협주유소" ...
...
 $ E_시군명        : chr  "화성시" "화성시" "화성시" "화성시" ...
 $ E_정류소명      : chr  "반월1통.빅마켓앞" "삼성전자" "능동주공단지" "수직리" ...
 $ 노선명          : chr  "17" "17" "17" "17" ...
> # 개별 이동 정보 확인
> head(route_sta[, c("암호화카드번호", "노선명", "S_정류소명", "E_정류소명")])
  암호화카드번호 노선명                                S_정류소명                       E_정류소명
1   900474615375     17 현대모닝사이드1차아파트.새마을입구.신현리                 반월1통.빅마켓앞
2   900109571235     17                     성남아트센터.태원고교                         삼성전자
3   900456269492     17        금광1.2동주민센터.중원어린이도서관                     능동주공단지
4   100073476942     17                          뉴광주농협주유소                           수직리
5   900517750322     17                                문형삼거리                   신미주아파트앞
6   900114055502     17                         보건소.공설운동장 동문아파트.협성대학교.봉담중고교

> ## 이용자가 많은 버스노선 추출
> # 노선별 이용 건수를 피벗테이블로 작성하기
> bus_usr <- as.data.frame(table(route_sta$노선명))
> # 컬럼명 변경
> colnames(bus_usr) <- c("line", "Freq")
> # 노선별 이용 건수 sortig => 빈도수가 많은 것부터
> bus_usr <- bus_usr %>%
+   arrange(desc(Freq))
> # 비율(percentage) 보기
> bus_usr$pcnt <- round((bus_usr$Freq/sum(bus_usr$Freq))*100, 1)
> # 노선별 이용건수 상위 5개 보기
> head(bus_usr, 5)
  line  Freq pcnt
1 92-1 18702  8.0
2 13-5 16748  7.1
3   98  9607  4.1
4 62-1  8312  3.5
5  7-1  7604  3.2

> ## 노선별 누적 이용자 비율 분석
> # 노선별 누적 비율 계산
> bus_usr <- bus_usr %>%
+   mutate(cum = round((cumsum(Freq)/sum(Freq)) * 100, 1))
> # 상위 42개 노선이 전체 이동량의 87.3%를 차지하고 있음
> head(bus_usr[39:42,])
    line Freq pcnt  cum
39    51 1645  0.7 85.4
40   5-1 1644  0.7 86.1
41     7 1634  0.7 86.8
42 720-1 1174  0.5 87.3
> # 저장
> save(bus_usr, file = "./01_save/03_001_bus_usr.rdata")
> # 누적 비율 차트
plot(bus_usr$cum, type = "l", xlim = c(0,100))
> abline(v = 42, col = "red", lwd = 3, lty = 2)


> ## 이동 발생 시간대 분석
> hist <- hist(trip_chain$start_hour, plot = FALSE)
plot(hist, xaxt = "n", xlab = "시간", ylab = "건수",
+      main = "시간대별 trip 발생량", col = "blue")
axis(1, hist$mids, labels = c(5:23))

>

> # Step 2 : 이용량 많은 버스노선 정류장 위치 알아보기

> ## 정류장 정보 추출
> # 버스노선 번호 추출
> bus_line <- as.character(bus_usr[1:42, 1])
> # 노선 - 정류장 매핑 테이블 불러오기
> load("./01_save/01_004_route_sta.rdata")
> # 이용량 많은 42개 버스노선의 정류장만 추출
> bus_line <- filter(route_sta, grepl(paste(as.character(bus_line), collapse = "|"), route_sta$bus_line_no))
> # 버스노선 중 대상 지역에 위치하는 정류장 정보만 추출
> patterns <- c("수원시", "화성시", "용인시", "오산시")
> bus_line <- filter(bus_line, grepl(paste(patterns, collapse = "|"), 시군명))
> # 중복되는 노선 지우기
> head(bus_line)
  station_id bus_line_no bus_line_no_seq            station_nm 이비카드정류장ID WGS84위도 WGS84경도 시군명
1  200000069        27-1              11   율전성당.성균관대역          4116713  37.30135  126.9725 수원시
2  200000070          27              53   체육고교.신안아파트          4116712  37.30305  126.9755 수원시
3  200000088        27-1              41  화서역.화서주공3단지          4116700  37.28670  126.9914 수원시
4  200000100          27              46 장안등기소.성우아파트          4108173  37.29632  126.9834 수원시
5  200000101          27              45            천천중학교          4108175  37.29913  126.9821 수원시
6  200000103          27              42            신안아파트          4108178  37.30272  126.9756 수원시
               정류소명
1   율전성당.성균관대역
2   체육고교.신안아파트
3  화서역.화서주공3단지
4 장안등기소.성우아파트
5            천천중학교
6            신안아파트
> bus_line <- bus_line[!duplicated(bus_line[c(2,4)]), ]
> bus_line <- bus_line %>%
+   arrange(bus_line_no, bus_line_no_seq)
> head(bus_line)
  station_id bus_line_no bus_line_no_seq          station_nm 이비카드정류장ID WGS84위도 WGS84경도 시군명
1  228001584        10-7               1          백암터미널          4176507  37.16525  127.3744 용인시
2  233000387        10-7               2        발안만세시장          4170543  37.13330  126.9094 화성시
3  228001998        10-7               3          백암중고교          4196851  37.16427  127.3700 용인시
4  233001558        10-7               3 발안삼거리.바다마트          4130357  37.13592  126.9109 화성시
5  228001532        10-7               4  백암초.중.고등학교          4196849  37.16332  127.3680 용인시
6  228000625        10-7               5              근창리          4150573  37.16050  127.3620 용인시
             정류소명
1          백암터미널
2        발안만세시장
3          백암중고교
4 발안삼거리.바다마트
5  백암초.중.고등학교
6              근창리
> # 노선별 이용량 결합
> bus_line <- merge(bus_line, bus_usr[1:42, 1:3], by.x = "bus_line_no", by.y = "line", all.x = TRUE)
> bus_line <- na.omit(bus_line)
> # 저장
> save(bus_line, file = "./01_save/03_002_bus_line.rdata")

> ## 정류장 정보를 공간 포인트로 만들기
> # 좌표값 추출
> library(tidyr)
> library(sp)
> coords <- bus_line %>% select(WGS84경도, WGS84위도)
> data <- bus_line[, 1:11]
> # 투영
> crs <- CRS("+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0")
> sta_pnt <- SpatialPointsDataFrame(coords = coords, data = data, proj4string = crs)
> # 저장
> save(sta_pnt, file = "./01_save/03_003_sta_pnt.rdata")
> # 시각화
> library(tmap)
> qtm("Hwaseong")
Switching to view mode. Run tmap_mode("plot") or simply ttm() to switch back to plot mode.
qtm("Hwaseong") +
+   tm_basemap("OpenStreetMap") +
+   # 이용자 1% 이상 노선

+   qtm(subset(sta_pnt, sta_pnt@data$pcnt > 1), symbols.col = "cadetblue2", symbols.size = .05) +
+   # 이용자 2% 이상 노선
+   qtm(subset(sta_pnt, sta_pnt@data$pcnt > 2), symbols.col = "burlywood3", symbols.size = .1) +
+   # 이용자 3% 이상 노선
+   qtm(subset(sta_pnt, sta_pnt@data$pcnt > 3), symbols.col = "orange", symbols.size = .3) +
+   # 이용자 4% 이상 노선
+   qtm(subset(sta_pnt, sta_pnt@data$pcnt > 4), symbols.col = "red", symbols.size = .5)
Warning message:
qtm called without shape objects cannot be stacked 

> head(subset(sta_pnt, sta_pnt@data$pcnt > 4))
    bus_line_no station_id bus_line_no_seq                station_nm 이비카드정류장ID WGS84위도 WGS84경도 시군명
221        13-5  233002505               6         향남주공5단지입구          4119431  37.11412  126.9054 화성시
222        13-5  233001026              21 화성중앙병원.발안초등학교          4130354  37.13118  126.9115 화성시
223        13-5  233002413              42             향남부영3단지          4120518  37.11895  126.9116 화성시
224        13-5  233002411              40           향남주공1.2단지          4120516  37.11882  126.9159 화성시
225        13-5  233002074              35              향남고등학교          4120441  37.12772  126.9268 화성시
226        13-5  233001854              36       한일베라체.행정초교          4199618  37.12602  126.9242 화성시
                     정류소명  Freq pcnt
221         향남주공5단지입구 16748  7.1
222 화성중앙병원.발안초등학교 16748  7.1
223             향남부영3단지 16748  7.1
224           향남주공1.2단지 16748  7.1
225              향남고등학교 16748  7.1
226       한일베라체.행정초교 16748  7.1
> max(sta_pnt@data$pcnt)
[1] 7.1
> # 화성시 외곽에서 시내로 이동하는 노선 13-5에 이용객이 집중된 것으로 판단된다. 해당 지역의 대중교통노선이나 버스 배차 간격을 확대할 필요가 있는 것으로 판단된다.

>

> ## 5 기초 분석 2 : 집계구별 이동 특성


> # Step 1 : 집계구별 이동 계산

> ## 정류장 데이터와 집계구의 공간 조인
> # 데이터 불러오기
> load("./01_save/02_003_trip_chain.rdata")
> load("./01_save/01_002_fishnet.rdata")
> load("./01_save/03_003_sta_pnt.rdata")
> load("./01_save/01_001_admin.rdata")
> # 공간 조인
> install.packages("spatialEco")
> require(spatialEco)
> sta_pnt <- point.in.poly(sta_pnt, fishnet)
> head(sta_pnt[, c(1,4,10,12)])
   bus_line_no                 station_nm Freq   id
90       116-2 동수원세무소.영통역2번출구 3872 2173
91       116-2 영통역7번출구.영덕고등학교 3872 2173
92       116-2               한화꿈에그린 3872 2636
93       116-2               공공청사부지 3872 2728
94       116-2           영통차고지(경유) 3872 2081
95       116-2             한림대병원(중) 3872 2450
> # 저장
> save(sta_pnt, file = "./01_save/04_001_sta_pnt.rdata")

> ## 승하차 정류장 데이터에 집계구 아이디 추가하기
> head(trip_chain[, c(3,60,65)])
  암호화카드번호                   S_정류소명 E_정류소명
1   900130818940                   병점사거리 하북차고지
2   100519260268                   병점사거리 하북차고지
3   900018471815                   롯데시네마 하북차고지
4   900016616508              반도.모아아파트 하북차고지
5   100511197656               신미주아파트앞    하북4리
6   900497235853 병점입구.화남아파트.병점육교    하북4리
> tail(trip_chain[, c(3,60,65)])
       암호화카드번호     S_정류소명        E_정류소명
206820   900500081540 두산신일아파트 단국대.평화의광장
206821   900494106715   반석초등학교 단국대.평화의광장
206822   900520373625 메타폴리스(중) 단국대.평화의광장
206823   900470527479       쌍용예가 단국대.평화의광장
206824   900514973139   한빛마을(중) 단국대.평화의광장
206825   900111667300        와우2리 단국대.평화의광장
> trip_chain <- merge(trip_chain, sta_pnt@data[, c(5,12)], by.x = "승차역ID1", by.y = "이비카드정류장ID")
> trip_chain <- merge(trip_chain, sta_pnt@data[, c(5,12)], by.x = "최종하차역ID", by.y = "이비카드정류장ID")
> tail(trip_chain[, c(3, 60, 65, 66, 67)])
       암호화카드번호   S_정류소명        E_정류소명 id.x id.y
161561   900455093812 한빛마을(중) 단국대.평화의광장 2541 1535
161562   900514973139 한빛마을(중) 단국대.평화의광장 2541 1535
161563   900455093812 한빛마을(중) 단국대.평화의광장 2541 1535
161564   900514973139 한빛마을(중) 단국대.평화의광장 2541 1535
161565   900514973139 한빛마을(중) 단국대.평화의광장 2541 1535
161566   900514973139 한빛마을(중) 단국대.평화의광장 2541 1535
> save(trip_chain, file = "./01_save/04_002_trip_chain.rdata")

> ## trip_chain에서 필요한 정보만 추출
> keeps <- c("id.x", "id.y", "승차역ID1", "최종하차역ID", "총이용객수", "환승횟수")
> grid_chain <- trip_chain[keeps]
> head(grid_chain)
  id.x id.y 승차역ID1 최종하차역ID 총이용객수 환승횟수
1 2630 3185   4100049      4100017          1        1
2 2630 3185   4100049      4100017          1        1
3 2630 3185   4100049      4100017          1        1
4 2541 3185   4170481      4100017          1        1
5 2630 3185   4100049      4100017          1        1
6 2541 3185   4170481      4100017          1        1
> rm("trip_chain"); rm("keeps")
> save(grid_chain, file = "./01_save/04_003_grid_chain.rdata")

>

> # Step 2 : 집계구별 출발, 도착 이동량 특성 분석

> ## 출발지 기준 총 이용객 수, 평균 환승 횟수 분석
> # 라이브러리 불러오기
> library(dplyr)
> library(sp)
> library(sf)
> # 출발지 기준 집계구 분석
> grid_in <- grid_chain %>%
+   group_by(id.x) %>%
+   summarize_if(is.numeric, sum) %>%
+   dplyr::select(id.x, 총이용객수, 환승횟수)
> # 평균 환승 횟수 계산
> grid_in$평균환승 <- round((grid_in$환승횟수 / grid_in$총이용객수), 1)
> # 컬럼 이름 정리하기
> colnames(grid_in)[1] <- c("id")
> colnames(grid_in)
[1] "id"         "총이용객수" "환승횟수"   "평균환승"  
> # s3(spatial polygon dataframe) => s4 포맷으로 변환
> fishnet_2 <- as(fishnet, "sf")
> # grid_in 결합
> fishnet_2 <- full_join(fishnet_2, grid_in, by = "id")
> head(fishnet_2)
Simple feature collection with 6 features and 4 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 126.5062 ymin: 37.47342 xmax: 126.5662 ymax: 37.48342
CRS:           +proj=longlat +datum=WGS84 +no_defs
  id 총이용객수 환승횟수 평균환승                       geometry
1  1         NA       NA       NA POLYGON ((126.5062 37.48342...
2  2         NA       NA       NA POLYGON ((126.5162 37.48342...
3  3         NA       NA       NA POLYGON ((126.5263 37.48342...
4  4         NA       NA       NA POLYGON ((126.5362 37.48342...
5  5         NA       NA       NA POLYGON ((126.5463 37.48342...
6  6         NA       NA       NA POLYGON ((126.5563 37.48342...
> # 저장
> save(fishnet_2, file = "./01_save/04_004_fishnet_2.rdata")

> ## 출발지 기준 => "총이용객수", "환승횟수", "평균환승" 플로팅
> # 총이용객수
> library(tmap)
> tm_shape(fishnet_2) +
+   tm_polygons("총이용객수", alpha = 0.6, border.col = "gray50", border.alpha = .2, colorNA = NULL) +
+   tm_shape(admin, alpha = 0.1) + tm_borders()

> # 총이용객수는 도심이 외곽 지역보다 많은 것으로 나타났다.

> # 평균환승
tm_shape(fishnet_2) +
+   tm_polygons("평균환승", alpha = 0.6, border.col = "gray50", border.alpha = .2, colorNA = NULL) +
+   tm_shape(admin, alpha = 0.1) + tm_borders()

> # 도시 외곽 지역 주로 용인에서 출발하는 사람들은 평균 환승 횟수가 많은 것으로 나타났다.

> ## 도착지 기준 총 이용객 수, 평균 환승 횟수 분석
> # 도착지 기준 집계구 분석
> grid_out <- grid_chain %>%
+   group_by(id.y) %>%
+   summarize_if(is.numeric, sum) %>%
+   dplyr::select(id.y, 총이용객수, 환승횟수)
> # 평균 환승 횟수 계산
> grid_out$평균환승 <- round((grid_out$환승횟수 / grid_out$총이용객수), 1)
> # 컬럼 이름 정리하기
> colnames(grid_out)[1] <- c("id")
> colnames(grid_out)
[1] "id"         "총이용객수" "환승횟수"   "평균환승"  
> # s3(spatial polygon dataframe) => s4 포맷으로 변환
> fishnet_2 <- as(fishnet, "sf")
> # grid_out 결합
> fishnet_2 <- full_join(fishnet_2, grid_out, by = "id")
> head(fishnet_2)
Simple feature collection with 6 features and 4 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 126.5062 ymin: 37.47342 xmax: 126.5662 ymax: 37.48342
CRS:           +proj=longlat +datum=WGS84 +no_defs
  id 총이용객수 환승횟수 평균환승                       geometry
1  1         NA       NA       NA POLYGON ((126.5062 37.48342...
2  2         NA       NA       NA POLYGON ((126.5162 37.48342...
3  3         NA       NA       NA POLYGON ((126.5263 37.48342...
4  4         NA       NA       NA POLYGON ((126.5362 37.48342...
5  5         NA       NA       NA POLYGON ((126.5463 37.48342...
6  6         NA       NA       NA POLYGON ((126.5563 37.48342...
> # 저장
> save(fishnet_2, file = "./01_save/04_005_fishnet_2.rdata")

> # 총이용객수
tm_shape(fishnet_2) +
+   tm_polygons("총이용객수", alpha = 0.6, border.col = "gray50", border.alpha = .2, colorNA = NULL) +
+   tm_shape(admin, alpha = 0.1) + tm_borders()

> # 총이용객수는 도심이 외곽 지역보다 많은 것으로 나타났다.

> # 평균환승
tm_shape(fishnet_2) +
+   tm_polygons("평균환승", alpha = 0.6, border.col = "gray50", border.alpha = .2, colorNA = NULL) +
+   tm_shape(admin, alpha = 0.1) + tm_borders()

> # 주로 도착지가 용인인 경우 평균 환승 횟수가 많은 것으로 나타났다.

 

 

 

 

 

 

 

출처 : 김철민, ⌜공공데이터로 배우는 R 데이터분석 with 샤이니⌟, 이지스퍼블리싱, 2022