2026-01-08
역동성과 상호작용성이 부가된 시각화
구현 방식
임베딩(embedding): 외부 콘텐츠 삽입
동적ㆍ반응형 내용 요소의 직접 제작
개념
외부 웹 사이트/앱의 콘텐츠를 현재 웹 사이트/앱 안에 삽입하여 보여주는 기법
HTML의 iframe 태그 활용
별도의 페이지 전환 없이 다른 서비스나 시각화 결과를 한 웹 앱에 통합
일부 사이트는 임베딩 불가능
대상
동적ㆍ반응형 시각화 웹 사이트/앱
상호작용형 교육ㆍ협업 도구: Padlet, Mentimeter, Google Forms 등
동영상: YouTube
<iframe src="https://ourworldindata.org/explorers/population-and-demography?indicator=Fertility+rate&Sex=Both+sexes&Age=Total&Projection+scenario=Medium&country=Asia+%28UN%29~Europe+%28UN%29~Africa+%28UN%29~Oceania+%28UN%29~Northern+America+%28UN%29~Latin+America+and+the+Caribbean+%28UN%29~OWID_WRL~KOR&tab=chart&hideControls=true" loading="lazy" style="width: 70%; height: 600px; border: 0px none;" allow="web-share; clipboard-write"></iframe>
테이블 역시 시각화의 일부
데이터 변형 및 요약을 거친 정적 테이블
시각성이 가미된 정적 테이블
인터랙티브 테이블
테이블 디자인 원칙(핸즈온 데이터 시각화, 2022)
열 제목을 데이터 상단에 눈에 띄게 만들어라.
밝은 음영을 사용해 열이나 행을 구분하라.
읽기 쉽도록 텍스트는 왼쪽 정렬하고 숫자는 오른쪽 정렬하라.
레이블을 첫 번째 행에만 배치하여 중복을 피하라.
테이터를 그룹화 및 정렬하여 의미 있는 패턴을 강조하라.
https://towardsdatascience.com/exploring-the-gt-grammar-of-tables-package-in-r-7fff9d0b40cd
https://twitter.com/DavidGohel/status/1376892794627883016/photo/1
| JS 라이브러리 | R 래퍼 패키지 |
|---|---|
| DataTables | DT |
| Tanstack Table | reactable |
DataTables: 자바스크립트 라이브러리
R 래퍼 패키지: DT
Pagination: 페이지 이동 기능
Instant search: 즉각적 검색 기능(Search에 타이핑하기 시작하면 즉각적으로 검색 결과 보여줌)
Ordering/sorging: 컬럼 정렬 기능
Multi-column ordering: 다중 컬럼 정렬 기능(컬럼 하나를 선택한 후 ctrl을 누른 상태에서 다른 컬럼을 선택)
Filtering: 값 추림 기능
Editable: 셀 값 수정 기능
Buttons: 셀 숨기기 기능, CSV, PDF, XLSX 등의 확장자로 내보내기 등을 수행하는 버튼 생성 기능
library(gganimate)
P <- gapminder |>
ggplot(aes(x = gdpPercap, y = lifeExp, size = pop, color = continent)) +
geom_point(show.legend = FALSE, alpha = 0.7) +
scale_x_log10() +
scale_size(range = c(2, 12))
P + transition_time(year) +
labs(title = "Year: {frame_time}")
ggplot2 패키지의 확장
상호작용형 기하객체(geometry): geom_*_interactive
상호작용형 시각속성(aethetics)
toolitp
onclick
data_id
웹북: ggiraph-book
library(tidyverse)
library(ggiraph)
data <- mtcars |> rownames_to_column(var = "carname")
gg_point <- ggplot(data = data) +
geom_point_interactive(
aes(x = wt, y = qsec, color = disp,
tooltip = carname, data_id = carname)
) +
theme_minimal()
girafe(ggobj = gg_point)| JS 라이브러리 | R 래퍼 패키지 |
|---|---|
| Plotly | plotly |
| D3 | r2d3 |
| Highcharts | highcharter |
| ECharts | echarts4r, echarty |
| dygraphs | dygraphs |
| Google Charts | googleVis |
| Chart.js | chartjs |
| three.js | r3js |
Plotly: 자바스크립트 라이브러리
R 래퍼 패키지: plotly package
plot_ly() 함수
ggplotly() 함수
gapminder |>
plot_ly(
x = ~log10(gdpPercap),
y = ~lifeExp,
text = ~paste(
"Country: ", country,
"<br>GDP per capita: ", gdpPercap,
"<br>Life Expectancy at Birth:", lifeExp
)
) |>
add_markers(
color = ~continent,
size = ~pop,
frame = ~year,
marker = list(sizeref = 0.2, sizemode = "area")
)ggplotly() 함수P <- gapminder |>
filter(year == 2007) |>
ggplot(aes(x = gdpPercap, y = lifeExp, color = continent)) +
geom_point() +
scale_color_brewer(palette = "Set2") +
theme_minimal()
ggplotly(P)library(tidyverse)
library(echarts4r)
my_data <- read_rds("D:/My R/Population Geography/wpp_2024.rds")
data_sel <- my_data |>
filter(
type == "Subregion" | region_name == "Northern America",
year == 2025
)
data_sel |>
arrange(median_age) |>
mutate(
median_age = round(median_age, digits = 1)
) |>
group_by(Region_NM) |>
e_charts(region_name, width = "100%", height = "800px") |>
e_bar(serie = median_age, stack = "grp") |>
e_color() |>
e_flip_coords() |>
e_x_axis(name = "Median Age") |>
e_tooltip() |>
e_legend()데이터 레이어
국가 경계, 호수, 그래티큘: 벡터(vector) 데이터
인구밀도, 수심: 래스터(raster) 데이터
데이터 원천
Natural Earth Data: 국가 경계, 호수, 그래티큘, 수심
NASA’s Socioeconomic Data and Applications Center (SEDAC): 인구 밀도
투영법: 로빈슨 도법(Robinson projection)
지도화 기법: 컬러, 범례, 주기 표기 등
벡터(vector) 데이터
포인트, 라인, 폴리곤
형상 데이터 + 속성 데이터
래스터(raster) 데이터
그리드 셀(grid cell)
일체형
벡터 데이터: 형상 데이터 + 속성 데이터
형상 데이터
지리공간적 객체 자체에 대한 데이터
포인트(점), 라인(선), 폴리곤(면)으로 구분
버텍스(vertex)의 좌표값
속성 데이터
지리공간적 객체가 보유한 속성
기존 일반 데이터와 동일
| 구분 | 함수 |
|---|---|
| 읽고 쓰기 |
st_read(), st_write(), read_sf(), write_sf()
|
| 투영 관련 |
st_crs(), st_transform()
|
| 기하 측정 |
st_area(), st_length(), st_perimeter(), st_distance()
|
| 기하 변형 |
st_centroid(), st_buffer(), st_boundary(), st_simplify()
|
| 기하 생성 |
st_point(), st_voronoi() , st_convex_hull(), st_make_grid()
|
| 기하 검토 |
st_is_valid(), st_make_valid()
|
| 기하 중첩 |
st_filter(), st_intersection(), st_union(), st_crop()
|
| 기타 |
st_coordinates(), st_cast(), st_as_sf(), st_graticule(), st_join()
|
Simple feature collection with 229 features and 9 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: 746255.1 ymin: 1458982 xmax: 1387941 ymax: 2068161
Projected CRS: KGD2002 / Unified CS
First 10 features:
SGG1_CD SD_CD SD_NM SG1_CD SG1_NM SGG1_NM SGG1_FNM
1 11010 11 서울특별시 11 서울특별시 종로구 서울특별시 종로구
2 11020 11 서울특별시 11 서울특별시 중구 서울특별시 중구
3 11030 11 서울특별시 11 서울특별시 용산구 서울특별시 용산구
4 11040 11 서울특별시 11 서울특별시 성동구 서울특별시 성동구
5 11050 11 서울특별시 11 서울특별시 광진구 서울특별시 광진구
6 11060 11 서울특별시 11 서울특별시 동대문구 서울특별시 동대문구
7 11070 11 서울특별시 11 서울특별시 중랑구 서울특별시 중랑구
8 11080 11 서울특별시 11 서울특별시 성북구 서울특별시 성북구
9 11090 11 서울특별시 11 서울특별시 강북구 서울특별시 강북구
10 11100 11 서울특별시 11 서울특별시 도봉구 서울특별시 도봉구
Eng_NM Chn_NM geometry
1 Jongno-gu 鐘路區 MULTIPOLYGON (((951756.1 19...
2 Jung-gu 中區 MULTIPOLYGON (((955244.1 19...
3 Yongsan-gu 龍山區 MULTIPOLYGON (((952446.3 19...
4 Seongdong-gu 城東區 MULTIPOLYGON (((957375.8 19...
5 Gwangjin-gu 廣津區 MULTIPOLYGON (((962216.2 19...
6 Dongdaemun-gu 東大門區 MULTIPOLYGON (((959605.5 19...
7 Jungnang-gu 中浪區 MULTIPOLYGON (((962496.9 19...
8 Seongbuk-gu 城北區 MULTIPOLYGON (((956780.3 19...
9 Gangbuk-gu 江北區 MULTIPOLYGON (((956574.1 19...
10 Dobong-gu 道峰區 MULTIPOLYGON (((958126.2 19...

속성 데이터
csv 파일: readr 패키지의 read_csv() 함수
엑셀 파일: readxl 패키지의 read_excel() 함수
Open API를 통해 수집: tibble 객체
형상 데이터와 속성 데이터의 결합: dplyr 패키지의 left_join() 함수
왼편: 형상 데이터
오른편: 속성 데이터
데이터 형식
패키지: terra 패키지
불러오기: rast()
변환하기: project(), mosaic(), crop()
계산하기: global(), focal(), zonal()
수 많은 다른 함수들
https://datacarpentry.github.io/organization-geospatial/03-crs.html
PROJ 정형문자열
EPSG 숫자코드
| 투영법 | PROJ 파라미터 |
|---|---|
| 정적원통 도법 Equal Area Cylindrical | +proj=cea |
| 컴펙트 밀러 도법 Compact Miller | +proj=comill |
| 에케르트 IV 도법 Eckert IV | +proj=eck4 |
| 정거원통 도법 Equidistant Cylindrical | +proj=eqc |
| 구드 도법 Goode Homolosine | +proj=goode |
| 단열형 구드 도법 Interrupted Goode Homolosine | +proj=igh |
| 메르카토르 도법 Mercator | +proj=merc |
| 몰바이데 도법 Mollweide | +proj=moll |
| 로빈슨 도법 Robinson | +proj=robin |
| 시뉴소이드 도법 Sinusoidal | +proj=sinu |
| 빈켈트리펠 도법 Winkel Tripel | +proj=wintri |
| 적용 스케일 | EPSG 숫자코드 | 설명 |
|---|---|---|
| 전세계 | EPSG:4326 | WGS84, 측지좌표계, GPS에 사용 |
| EPSG:3857 | 웹 메르카토르 도법, 구글 맵스, 오픈스트리트맵에서 사용 | |
| EPSG:7789 | ITRF2014 | |
| 미국 | EPSG:2163 | 알베르스 정적원추 도법 |
| 유럽 | EPSG:3035 | 람베르트 정적방위 도법 |
| 우리나라 | EPSG:5179 | UTM-K |
| EPSG:5185 | 서부원점 | |
| EPSG:5186 | 중부원점 | |
| EPSG:5187 | 동부원점 | |
| EPSG:5188 | 동해원점 |
정적(static) 지도
동적(animated) 지도
인터랙티브(interactive) 지도
plotly 패키지의 ggplotly() 함수
ggiraph 패키지
leaflet 패키지: Leaflet JS 라이브러리의 래퍼 패키지
“지도도 그래프다” 관점: 일반성
geom_sf(), coord_sf()
“지도는 지도이다” 관점: 특수성
world_map <- ggplot() +
geom_sf(data = world_data, aes(fill = TFR, text = name_long)) +
coord_sf(crs = "+proj=robin") +
scale_fill_viridis_c() +
scale_x_continuous(breaks = seq(-180, 180, 30)) +
scale_y_continuous(breaks = c(-89.5, seq(-60, 60, 30), 89.5)) +
theme(
panel.background = element_rect("white"),
panel.grid = element_line(color = "gray80")
)
world_map
library(tmap)
tm_world_map <- tm_shape(world_data, crs = "+proj=robin") +
tm_graticules(
labels.show = FALSE,
x = seq(-180, 180, 30),
y = c(-89.5, seq(-60, 60, 30), 89.5)
) +
tm_polygons(
fill = "TFR",
fill.scale = tm_scale_continuous(values = "viridis")
) +
tm_layout(frame = FALSE)
tm_world_map
library(ggspatial)
sigungu_data <- sigungu_data |>
mutate(
index_class = case_when(
index < 0.2 ~ "1",
index >= 0.2 & index < 0.5 ~ "2",
index >= 0.5 & index < 1.0 ~ "3",
index >= 1.0 & index < 1.5 ~ "4",
index >= 1.5 ~ "5"
),
index_class = fct(index_class, levels = as.character(1:5))
)
class_color <- c("1" = "#d7191c", "2" = "#fdae61",
"3" = "#ffffbf", "4" = "#a6d96a",
"5" = "#1a9641")
ggplot_map <- ggplot() +
geom_sf(
data = sigungu_data,
aes(fill = index_class, text = SGG1_FNM),
show.legend = TRUE
) +
geom_sf(
data = sido_shp,
fill = NA,
lwd = 0.5
) +
scale_fill_manual(
name = "Classes",
labels = c("< 0.2", "0.2 ~ 0.5", "0.5 ~ 1.0",
"1.0 ~ 1.5", ">= 1.5"),
values = class_color, drop = FALSE
) +
annotation_scale(
location = "br",
bar_cols = c("gray40", "white"),
width_hint = 0.4
)
ggplot_map
class_color <- c("#d7191c", "#fdae61", "#ffffbf", "#a6d96a", "#1a9641")
tmap_map <- tm_graticules(labels.cardinal = TRUE) +
tm_shape(sigungu_data) +
tm_polygons(
fill = "index", id = "SGG1_FNM",
fill.scale = tm_scale_intervals(
values = class_color,
breaks = c(0, 0.2, 0.5, 1.0, 1.5, Inf),
labels = c("< 0.2", "0.2 ~ 0.5", "0.5 ~ 1.0",
"1.0 ~ 1.5", ">= 1.5")
),
fill.legend = tm_legend(title = "Classes")
) +
tm_shape(sido_shp) + tm_borders(lwd = 1.5) +
tm_scalebar(breaks = seq(0, 200, 50))
tmap_map
ggplotly() 함수library(ggiraph)
sigungu_data <- sigungu_data |>
mutate(
index = format(index, digits = 4, nsmall = 4),
my_tooltip = str_c("Name: ", SGG1_FNM, "\n Index: ", index)
)
gg <- ggplot() +
geom_sf_interactive(
data = sigungu_data,
aes(fill = index_class, tooltip = my_tooltip, data_id = SGG1_FNM),
show.legend = TRUE
) +
geom_sf(data = sido_shp, fill = NA, lwd = 0.5) +
scale_fill_manual(
name = "Classes",
labels = c("< 0.2", "0.2 ~ 0.5", "0.5 ~ 1.0", "1.0 ~ 1.5", ">= 1.5"),
values = class_color, drop = FALSE
)
girafe(ggobj = gg) |>
girafe_options(opts_hover(css = "fill: gray"))library(leaflet)
world_data <- world_data |> filter(!is.na(TFR))
bins <- c(0, 1.5, 2.1, 3, 4, 5, Inf)
pal <- colorBin("YlOrRd", domain = world_data$TFR, bins = bins)
labels <- sprintf("<strong>%s</strong><br/>%g",
world_data$name_long, world_data$TFR) |> lapply(htmltools::HTML)
leaflet(world_data) |>
addProviderTiles(providers$Esri.WorldTopoMap) |>
addPolygons(
fillColor = ~pal(TFR), weight = 2, opacity = 1, color = "white",
dashArray = "3", fillOpacity = 0.6,
highlightOptions = highlightOptions(
weight = 5, color = "#666", dashArray = "",
fillOpacity = 0.6, bringToFront = TRUE
),
label = labels,
labelOptions = labelOptions(
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "15px", direction = "auto"
)
) |>
addLegend(
pal = pal, values = ~TFR, opacity = 0.6, title = NULL, position = "bottomright"
)library(tmap)
class_color <- c("#d7191c", "#fdae61", "#ffffbf", "#a6d96a", "#1a9641")
sigungu_data <- sigungu_data |> mutate(index = as.numeric(index))
tmap_mode(mode = "view")
my_tmap <- tm_shape(sigungu_data) +
tm_polygons(
fill = "index", fill_alpha = 0.6, col_alpha = 0.5,
popup.vars = c("지역소멸위험지수: " = "index"),
popup.format = list(index = list(digits = 3)),
id = "SGG1_FNM",
fill.scale = tm_scale_intervals(
values = class_color, breaks = c(0, 0.2, 0.5, 1.0, 1.5, Inf),
labels = c("< 0.2", "0.2~0.5", "0.5~1.0", "1.0~1.5", ">= 1.5")
),
fill.legend = tm_legend(title = "Classes")
) +
tm_shape(sido_shp) + tm_borders(lwd = 2)
my_tmap
https://sangillee.snu.ac.kr/