← R 실습 — 손으로 돌려 보기 차례
2편Loading Your Data
자료 읽기 — 세 가지 입력 형식
R 실습 — 손으로 돌려 보기STAGED+ 스터디
2.1 세 가지 입력 형식 (Three Input Formats)
현장에서 만나는 관계 자료는 사실상 세 모양 중 하나다.
| 형식 | 생김새 | 언제 쓰나 |
|---|---|---|
| 인접행렬 adjacency | 26 × 26, 이면 가 를 지명 | 사람 수가 적을 때. sna 함수에 그대로 넣을 수 있어 제일 편하다 |
| 엣지리스트 edge list | from, to, liking 3열 × 88행 | 사람이 많을 때(행렬은 대부분 0이라 낭비). 가중치를 붙이기 쉽다 |
| 노드표 node attributes | name, female, isei08_m, isei08_f 4열 × 26행 | 관계가 아니라 사람의 속성. 위 둘 중 하나와 반드시 짝을 이룬다 |
세 파일을 메모장으로 열면 이렇게 생겼다. 앞 몇 줄만, 그리고 앞 여섯 명까지만 옮겼다.
classroom-adjacency.csv 인접행렬
"","1003","1006","1009","1012","1015","1018", …
"1003",0,0,0,0,0,1, …
"1006",0,0,1,0,0,0, …
"1009",0,1,0,0,0,0, …
classroom-edges.csv 엣지리스트
"from","to","liking"
"1003","1018",3
"1003","1051",3
"1003","1072",4
"1006","1009",5
classroom-nodes.csv 노드표
"name","female","isei08_m","isei08_f"
"1003",FALSE,NA,25.71
"1006",TRUE,14.64,33.76
"1009",TRUE,28.48,37.22
초록으로 칠한 두 곳이 같은 사실을 적은 것이다 —
행렬에서 1003 행·1018 열의
1, 엣지리스트에서 "1003","1018",3 줄.
"1003이 1018을 지명했다"는 한 가지를 형식만 바꿔 적었다.
따옴표는 그 칸이 숫자가 아니라 문자열이라는 표시고,
read.csv()가 읽을 때 알아서 벗겨 낸다.
같은 관계를 세 형식이 어떻게 적는지 나란히 놓으면 이렇다. 여섯 명(1003 · 1006 · 1009 · 1012 · 1015 · 1018)만 잘라 온 것이다.
① 인접행렬 6 × 6 발췌
| 1003 | 1006 | 1009 | 1012 | 1015 | 1018 | |
|---|---|---|---|---|---|---|
| 1003 | 0 | 0 | 0 | 0 | 0 | 1 |
| 1006 | 0 | 0 | 1 | 0 | 0 | 0 |
| 1009 | 0 | 1 | 0 | 0 | 0 | 0 |
| 1012 | 0 | 0 | 0 | 0 | 1 | 0 |
| 1015 | 0 | 0 | 0 | 1 | 0 | 0 |
| 1018 | 0 | 0 | 0 | 0 | 0 | 0 |
② 엣지리스트
| from | to | liking |
|---|---|---|
| 1003 | 1018 | 3 |
| 1006 | 1009 | 5 |
| 1009 | 1006 | 2 |
| 1012 | 1015 | 5 |
| 1015 | 1012 | 2 |
| ⋮ 나머지 83줄 | ||
③ 노드표
| name | female |
|---|---|
| 1003 | FALSE |
| 1006 | TRUE |
| 1009 | TRUE |
| 1012 | TRUE |
| 1015 | TRUE |
| 1018 | FALSE |
행렬의 색칠한 칸 다섯 개 = 엣지리스트의 색칠한 줄 다섯 개 = 아래 그림의 화살표 다섯 개다. 셋은 같은 관계를 적는 세 가지 방법일 뿐이다.
왜 형식이 셋이나 되나 — 26명이면 행렬은 칸인데
그중 은 88칸뿐이다(밀도 0.135). 나머지 588칸은 전부 0이다.
학급이 커지면 행렬은 으로 불어나지만
엣지리스트는 관계 수만큼만 길어진다.
그래서 설문은 엣지리스트로 걷고, 계산은 행렬로 한다.
노드표는 둘 중 어느 쪽과도 짝을 이룬다.
D <- "발표/6장/자료/statnet_실습"
# ① 인접행렬 — 첫 열이 이름이므로 row.names = 1
adj <- as.matrix(read.csv(file.path(D, "classroom-adjacency.csv"),
row.names = 1, check.names = FALSE))
# ② 엣지리스트 — ID를 숫자로 읽으면 나중에 깨진다. 반드시 문자로
el <- read.csv(file.path(D, "classroom-edges.csv"), colClasses = "character")
el$liking <- as.numeric(el$liking)
# ③ 노드표
nod <- read.csv(file.path(D, "classroom-nodes.csv"),
colClasses = c("character", "logical", "numeric", "numeric"))
dim(adj); nrow(el); nrow(nod)
[1] 26 26
[1] 88
[1] 26
가장 흔한 초보 실수 두 개
- ID를 숫자로 읽는 것. 학생 번호
1003이 정수가 되면 igraph가 이를 "1003번째 정점"으로 오해해 정점 1003개짜리 그래프를 만든다.colClasses = "character"를 반드시 쓴다. - 행렬 이름과 노드표 이름의 순서가 다른 것. 확인 한 줄이면 된다.
identical(rownames(adj), nod$name) [1] TRUEFALSE가 나오면nod <- nod[match(rownames(adj), nod$name), ]로 맞춘다. 이걸 안 맞추면 성별이 엉뚱한 학생에게 붙는다 — 그림은 멀쩡히 그려지므로 눈치채기 어렵다.
2.2 객체 만들기 — network와 igraph (Creating Objects)
어느 형식이 어느 함수로 이어지는지부터 보면 아래 코드가 한눈에 들어온다.
인접행렬adjacency.csv
network(adj, directed = TRUE)
graph_from_adjacency_matrix(adj, mode = "directed")
읽을 때 row.names = 1로 첫 열을 이름으로 넘겨 둔다
엣지리스트edges.csv
graph_from_data_frame(el, vertices = nod)
network(as.matrix(el[, 1:2]), matrix.type = "edgelist")
노드표를 같이 주지 않으면 아무와도 이어지지 않은 1024가 빠져 25명이 된다
노드표nodes.csv
set.vertex.attribute(net, "female", nod$female)
V(g)$female <- nod$female
관계가 아니라 속성 — 반드시 위 둘 중 하나에 얹는다
# statnet 계보
net <- network::network(adj, directed = TRUE)
network::set.vertex.attribute(net, "female", nod$female)
# igraph 계보 — 행렬에서
g <- igraph::graph_from_adjacency_matrix(adj, mode = "directed")
igraph::V(g)$female <- nod$female
# igraph 계보 — 엣지리스트 + 노드표에서 (가중치까지 한 번에)
gw <- igraph::graph_from_data_frame(el, directed = TRUE, vertices = nod)
network::network.size(net); network::network.edgecount(net)
[1] 26
[1] 88
igraph::vcount(g); igraph::ecount(g)
[1] 26
[1] 88
graph_from_data_frame(vertices = nod)가 왜 좋은가 —
엣지리스트만 주면 아무에게도 지명받지 않고 아무도 지명하지 않은 학생이 사라진다.
이 학급의 1024번이 정확히 그런 학생이다. vertices=로 노드표를 같이 주면
26명이 모두 살아남고, 속성도 자동으로 붙는다.
2.3 intergraph으로 오가기 (Converting Between Packages)
g2 <- intergraph::asIgraph(net) # network → igraph
n2 <- intergraph::asNetwork(g) # igraph → network
igraph::vcount(g2); igraph::ecount(g2)
[1] 26
[1] 88
노드 26·간선 88이 그대로다. 속성도 따라온다. "igraph에만 있는 커뮤니티 탐지를 쓴 뒤, 결과를 sna 그림에 얹는" 식의 왕복이 실제로 자주 필요하다.
확인해 보면 놀라는 것 — 행렬 → igraph → 행렬로 되돌린 뒤 비교하면
FALSE가 나온다.
B <- igraph::as_adjacency_matrix(g, sparse = FALSE)
identical(adj, B)
[1] FALSE
all(adj == B) # 값은?
[1] TRUE
storage.mode(adj); storage.mode(B)
[1] "integer"
[1] "double"
값은 완전히 같고 저장 타입만 다르다.
identical()은 타입까지 보므로 FALSE가 나온 것이다.
자료가 망가진 게 아니다. 이런 비교에는 all(x == y)나 all.equal()을 쓴다.