Skip to content

Commit 19311b1

Browse files
committed
cleaned the extraction of instant dyn nets
1 parent e844089 commit 19311b1

File tree

9 files changed

+252
-30
lines changed

9 files changed

+252
-30
lines changed

src/common/_include.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,5 @@ source("src/static/plot_stats_base.R")
9090
source("src/static/plot_stats_comp.R")
9191

9292
source("src/dynamic/cumulative.R")
93+
source("src/dynamic/instant.R")
9394
source("src/dynamic/narr_smooth.R")

src/common/table_cols.R

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ COL_VOLUMES_BY_ARC <- "AvgVolumesByArc" # average number of volumes by arc
3434

3535
###############################################################################
3636
# chapters
37-
COL_CHAP <- "Chapter" # chapter number, relative to a (novel) volume
38-
COL_CHAP_ID <- "ChapterId"# unique chapter id (overall)
39-
COL_CHAPS <- "Chapters"# number of chapters in a volume/arc
37+
COL_CHAPTER <- "Chapter" # chapter number, relative to a (novel) volume
38+
COL_CHAPTER_ID <- "ChapterId"# unique chapter id (overall)
39+
COL_CHAPTERS <- "Chapters"# number of chapters in a volume/arc
4040
# TODO implement stat computation for chapters, which were added afterwards (to handle ASOIAF)
4141

4242

src/corpus/read_data.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ write.corpus.data <- function(
835835
1:nrow(chapter.stats),
836836
sapply(chapter.chars, function(chars) paste(chars,collapse="\t"))
837837
)
838-
colnames(tab) <- c(COL_CHAP_ID, COL_CHARS)
838+
colnames(tab) <- c(COL_CHAPTER_ID, COL_CHARS)
839839
file <- get.path.stats.corpus(object="chapter", char.det=char.det, pref="_chapter_chars")
840840
tlog(4,"Writing chapter chars \"",file,"\"")
841841
write.table(tab, file=paste0(file,".txt"), fileEncoding="UTF-8", sep="\t", quote=FALSE, row.names=FALSE, col.names=TRUE)

src/dev_ASOIAF.R

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ SERIES <- "ASOIAF"
2222
###############################################################################
2323
# load scripts
2424
source("src/common/_include.R")
25-
source("src/post/asoiaf/add_chapters.R")
26-
source("src/post/asoiaf/extr_dyn.R")
25+
source("src/post/asoiaf/_all.R")
2726

2827
# start logging
2928
start.rec.log(text=SERIES)
@@ -71,7 +70,7 @@ generate.static.plots.base(data=data)
7170
# extract dynamic networks
7271
extract.dyn.nets.asoiaf(data)
7372

74-
# compute and plot their stats
73+
# compute and plot a few measures
7574
# TODO
7675

7776

src/dynamic/cumulative.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
# pub.order: whether to consider volumes in publication vs. story order.
2424
# narr.unit: narrative unit to perform the aggregation (scene, volume, arc).
2525
#
26-
# returns: a sequence of graphs corresponding to a dynamic graph.
26+
# returns: a sequence of graphs corresponding to a cumulative dynamic graph.
2727
###############################################################################
2828
cum.graph.extraction <- function(inter.df, char.stats, scene.chars, scene.stats, volume.stats, filtered=FALSE, pub.order=TRUE, narr.unit=NA)
2929
{
@@ -61,7 +61,7 @@ cum.graph.extraction <- function(inter.df, char.stats, scene.chars, scene.stats,
6161
if(narr.unit=="scene")
6262
cur.unit <- sc.id
6363
else if(narr.unit=="chapter")
64-
cur.unit <- scene.stats[sc.idx,COL_CHAP_ID]
64+
cur.unit <- scene.stats[sc.idx,COL_CHAPTER_ID]
6565
else if(narr.unit=="volume")
6666
cur.unit <- scene.stats[sc.idx,COL_VOLUME_ID]
6767
else if(narr.unit=="arc")

src/dynamic/instant.R

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# This script extracts a standard dynamic network, as a sequence of static
2+
# graphs representing time slices. There is no aggregation here: a different
3+
# function allows extracting cumulative graphs.
4+
#
5+
# Vincent Labatut
6+
# 03/2023
7+
#
8+
# setwd("~/eclipse/workspaces/Networks/NaNet")
9+
# setwd("C:/Users/Vincent/Eclipse/workspaces/Networks/NaNet")
10+
# source("src/dynamic/instant.R")
11+
###############################################################################
12+
13+
14+
15+
16+
###############################################################################
17+
# Extracts a dynamic network based on the specified scenes.
18+
#
19+
# inter.df: dataframe containing the pairwise interactions.
20+
# char.stats: list of characters with their attributes.
21+
# scene.chars: which character appears in which scene.
22+
# scene.stats: allows retrieving scene durations.
23+
# volume.stats: allows ordering volumes by publication date or story-wise.
24+
# filtered: whether characters should be filtered or not.
25+
# pub.order: whether to consider volumes in publication vs. story order.
26+
# narr.unit: narrative unit of the dynamic graph (scene, volume, arc).
27+
#
28+
# returns: a sequence of graphs corresponding to a dynamic graph.
29+
###############################################################################
30+
inst.graph.extraction <- function(inter.df, char.stats, scene.chars, scene.stats, volume.stats, filtered=FALSE, pub.order=TRUE, narr.unit=NA)
31+
{
32+
# extract the graph
33+
tlog(2,"Extracting the scene sequence graph")
34+
gg <- extract.static.graph.scenes(
35+
inter.df=inter.df,
36+
char.stats=char.stats,
37+
volume.stats=volume.stats,
38+
scene.stats=scene.stats, scene.chars=scene.chars,
39+
ret.seq=TRUE, pub.order=pub.order
40+
)
41+
42+
# possiby compute the filtered version
43+
if(filtered)
44+
{ tlog(2,"Filtering the characters")
45+
filt.names <- char.stats[char.stats[,COL_FILTER]=="Discard",COL_NAME]
46+
if(length(filt.names)==0) stop("Empty list of filtered characters")
47+
gg <- future_lapply(gg, function(g) delete_vertices(g, v=intersect(filt.names,V(g)$name)))
48+
}
49+
50+
# possibly aggregate to handle the narrative unit
51+
res <- list()
52+
if(narr.unit!="scene")
53+
{ tlog(2,"Aggregating by ",narr.unit)
54+
prev.unit <- NA
55+
for(s in 1:length(gg))
56+
{#tlog(4,"Processing scene ",s,"/",length(gg))
57+
58+
# retrieve current scene graph
59+
sc.g <- gg[[s]]
60+
61+
# retrieve current narrative unit
62+
sc.id <- gg[[s]]$SceneId
63+
sc.idx <- which(scene.stats[,COL_SCENE_ID]==sc.id)
64+
if(narr.unit=="chapter")
65+
cur.unit <- scene.stats[sc.idx,COL_CHAPTER_ID]
66+
else if(narr.unit=="volume")
67+
cur.unit <- scene.stats[sc.idx,COL_VOLUME_ID]
68+
else if(narr.unit=="arc")
69+
cur.unit <- scene.stats[sc.idx,COL_ARC_ID]
70+
#tlog(4,"Current ",narr.unit,": ",cur.unit," (previous ",narr.unit,": ",prev.unit,")")
71+
72+
# first graph of a narrative unit
73+
if(is.na(prev.unit) || prev.unit!=cur.unit)
74+
{# possibly remove isolates in previous graph
75+
if(!is.na(prev.unit))
76+
{prev.g <- res[[length(res)]]
77+
prev.isolates <- which(degree(prev.g,mode="all")==0)
78+
prev.g <- delete_vertices(graph=prev.g, v=prev.isolates)
79+
res[[length(res)]] <- prev.g
80+
}
81+
82+
# add current scene graph in the sequence
83+
sc.g$NarrUnit <- paste0(narr.unit,"_",cur.unit)
84+
res[[length(res)+1]] <- sc.g
85+
prev.unit <- cur.unit
86+
}
87+
88+
# rest of the narrative unit
89+
else
90+
{prev.g <- res[[length(res)]]
91+
92+
# add current edges to previous graph
93+
if(gsize(sc.g)>0)
94+
{el <- as_edgelist(graph=sc.g, names=TRUE)
95+
for(e in 1:nrow(el))
96+
{#tlog(6,"e=",e," nrow(el)=",nrow(el))
97+
# edge already exists: increment weights
98+
if(are_adjacent(graph=prev.g, v1=el[e,1], v2=el[e,2]))
99+
{idx <- get.edge.ids(graph=prev.g, vp=el[e,])
100+
#tlog(6,"idx=",idx)
101+
E(prev.g)[idx]$Occurrences <- E(prev.g)[idx]$Occurrences + E(sc.g)$Occurrences[e]
102+
E(prev.g)[idx]$Duration <- E(prev.g)[idx]$Duration + E(sc.g)$Duration[e]
103+
}
104+
# otherwise: create new edge
105+
else
106+
prev.g <- add_edges(graph=prev.g, edges=el[e,],
107+
attr=list(Occurrences=E(sc.g)$Occurrences[e], Duration=E(sc.g)$Duration[e]))
108+
}
109+
}
110+
111+
# update last graph in result sequence
112+
res[[length(res)]] <- prev.g
113+
}
114+
}
115+
# remove isolates in last graph
116+
last.g <- res[[length(res)]]
117+
last.isolates <- which(degree(last.g,mode="all")==0)
118+
last.g <- delete_vertices(graph=last.g, v=last.isolates)
119+
res[[length(res)]] <- last.g
120+
}
121+
122+
# no aggregation needed for scenes, as it is the smallest narrative unit
123+
else
124+
{for(s in 1:length(gg))
125+
{# get current graph
126+
sc.g <- gg[[s]]
127+
# add narrative unit attribute to graph
128+
cur.unit <- gg[[s]]$SceneId
129+
sc.g$NarrUnit <- paste0(narr.unit,"_",cur.unit)
130+
# add graph to list
131+
res[[length(res)+1]] <- sc.g
132+
}
133+
}
134+
135+
# test: plot evolution of nbr of vertices
136+
#v.nbr <- sapply(res, gorder)
137+
#units <- 1:length(res)
138+
#x.labels <- sapply(res, function(g) g$NarrUnit)
139+
#plot(x=units, y=v.nbr, xaxt="n", xlab=paste0(narr.unit,"s"), ylab="Vertices", col="RED")
140+
#axis(side=1, at=units, labels=x.labels, las=2)
141+
142+
return(res)
143+
}
144+
145+
146+
147+
148+
###############################################################################
149+
# Record a dynamic graph as a series of graphs.
150+
#
151+
# gs: list of igraph objects representing a dynamic graph.
152+
# filtered: whether the characters have been filtered or not.
153+
# pub.order: whether to consider volumes in publication vs. story order.
154+
# char.det: character detection mode ("implicit" or "explicit").
155+
###############################################################################
156+
inst.write.graph <- function(gs, filtered, pub.order=TRUE, char.det=NA)
157+
{if(pub.order)# by publication order
158+
ord.fold <- "publication"
159+
else# by story order
160+
ord.fold <- "story"
161+
162+
# retrieve narrative unit
163+
narr.unit <- strsplit(gs[[1]]$NarrUnit, split="_")[[1]][1]
164+
165+
base.file <- get.path.data.graph(mode="scenes", char.det=char.det, net.type="instant", order=ord.fold, filtered=filtered, subfold=narr.unit, pref="inst")
166+
write.dynamic.graph(gs=gs, base.path=base.file)
167+
}
168+
169+
170+
171+
172+
###############################################################################
173+
# Read sequence of graphs representing a dynamic graph, based on a sequence of
174+
# graphml files, each one representing one step of the dynamic graph.
175+
#
176+
# filtered: whether the characters have been filtered or not.
177+
# remove.isolates: whether to remove isolates in each time slice.
178+
# pub.order: whether to consider volumes in publication vs. story order.
179+
# char.det: character detection mode ("implicit" or "explicit").
180+
# narr.unit: narrative unit used to extract the dynamic network (scene, volume, etc.).
181+
#
182+
# returns: list of igraph objects representing a dynamic graph.
183+
###############################################################################
184+
inst.read.graph <- function(filtered, remove.isolates=TRUE, pub.order=TRUE, char.det=NA, narr.unit=NA)
185+
{if(pub.order)# by publication order
186+
ord.fold <- "publication"
187+
else# by story order
188+
ord.fold <- "story"
189+
190+
base.file <- get.path.data.graph(mode="scenes", char.det=char.det, net.type="instant", order=ord.fold, filtered=filtered, subfold=narr.unit, pref="inst")
191+
gs <- read.dynamic.graph(base.file=base.file, remove.isolates=remove.isolates)
192+
193+
return(gs)
194+
}

src/post/asoiaf/_all.R

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Main script for the ASOIAF-specific functions.
2+
#
3+
# Vincent Labatut
4+
# 03/2023
5+
#
6+
# setwd("~/eclipse/workspaces/Networks/NaNet")
7+
# setwd("C:/Users/Vincent/Eclipse/workspaces/Networks/NaNet")
8+
# source("src/post/asoiaf/_all.R")
9+
###############################################################################
10+
11+
12+
13+
14+
source("src/post/asoiaf/add_chapters.R")
15+
source("src/post/asoiaf/extr_dyn.R")

src/post/asoiaf/add_chapters.R

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ add.chapters.asoiaf <- function(data)
9999
page.chapter.id <- sapply(1:nrow(page.stats), function(p) which(chapter.page.start.id<=p & chapter.page.end.id>=p))
100100
page.stats <- cbind(
101101
page.stats,
102-
#Chapter=map[page.chapter.id,COL_CHAP], # don't really need that, and moreover it's confusing
102+
#Chapter=map[page.chapter.id,COL_CHAPTER], # don't really need that, and moreover it's confusing
103103
ChapterId=page.chapter.id
104104
)
105105

@@ -109,40 +109,40 @@ add.chapters.asoiaf <- function(data)
109109
panel.chapter.id <- sapply(1:nrow(panel.stats), function(p) which(chapter.page.start.id<=panel.stats[p,COL_PAGE_ID] & chapter.page.end.id>=panel.stats[p,COL_PAGE_ID]))
110110
panel.stats <- cbind(
111111
panel.stats,
112-
#Chapter=map[panel.chapter.id,COL_CHAP],
112+
#Chapter=map[panel.chapter.id,COL_CHAPTER],
113113
ChapterId=panel.chapter.id
114114
)
115115

116116
# add chapter info to scene table
117117
scene.chapter.id <- sapply(1:nrow(scene.stats), function(s) which(chapter.page.start.id<=scene.stats[s,COL_PAGE_START_ID] & chapter.page.end.id>=scene.stats[s,COL_PAGE_END_ID]))
118118
scene.stats <- cbind(
119119
scene.stats,
120-
#Chapter=map[scene.chapter.id,COL_CHAP],
120+
#Chapter=map[scene.chapter.id,COL_CHAPTER],
121121
ChapterId=scene.chapter.id
122122
)
123-
chapter.scene.start.id <- sapply(1:nrow(map), function(c) min(which(scene.stats[,COL_CHAP_ID]==c)))
124-
chapter.scene.end.id <- sapply(1:nrow(map), function(c) max(which(scene.stats[,COL_CHAP_ID]==c)))
123+
chapter.scene.start.id <- sapply(1:nrow(map), function(c) min(which(scene.stats[,COL_CHAPTER_ID]==c)))
124+
chapter.scene.end.id <- sapply(1:nrow(map), function(c) max(which(scene.stats[,COL_CHAPTER_ID]==c)))
125125

126126
# add chapter info to interaction table
127127
inter.chapter.id <- sapply(1:nrow(inter.df), function(i) which(chapter.page.start.id<=inter.df[i,COL_PAGE_START_ID] & chapter.page.end.id>=inter.df[i,COL_PAGE_END_ID]))
128128
inter.df <- cbind(
129129
inter.df,
130-
#Chapter=map[inter.chapter.id,COL_CHAP],
130+
#Chapter=map[inter.chapter.id,COL_CHAPTER],
131131
ChapterId=inter.chapter.id
132132
)
133133

134134
# build character list by chapter
135-
chapter.chars <- sapply(1:nrow(map), function(c) sort(unique(unlist(scene.chars[scene.stats[,COL_CHAP_ID]==c]))))
135+
chapter.chars <- sapply(1:nrow(map), function(c) sort(unique(unlist(scene.chars[scene.stats[,COL_CHAPTER_ID]==c]))))
136136

137137
# add chapter info to character table
138138
tt <- table(unlist(chapter.chars))
139-
char.stats[match(names(tt),char.stats[,COL_NAME]),COL_CHAPS] <- tt
139+
char.stats[match(names(tt),char.stats[,COL_NAME]),COL_CHAPTERS] <- tt
140140

141141

142142
####################################
143143
# create new chapter table
144144
chapter.stats <- data.frame(
145-
1:nrow(map), map[,COL_CHAP], paste(map[,"PoV"],map[,"Number"]),
145+
1:nrow(map), map[,COL_CHAPTER], paste(map[,"PoV"],map[,"Number"]),
146146
map[,COL_VOLUME],
147147
map[,COL_PAGE_START], map[,COL_PAGE_END],
148148
chapter.arc,
@@ -158,7 +158,7 @@ add.chapters.asoiaf <- function(data)
158158
stringsAsFactors=FALSE, check.names=FALSE
159159
)
160160
colnames(chapter.stats) <- c(
161-
COL_CHAP_ID, COL_CHAP, COL_TITLE,
161+
COL_CHAPTER_ID, COL_CHAPTER, COL_TITLE,
162162
COL_VOLUME,
163163
COL_PAGE_START, COL_PAGE_END,
164164
COL_ARC,

src/post/asoiaf/extr_dyn.R

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,32 @@ extract.dyn.nets.asoiaf <- function(data)
4646
for(filtered in c(FALSE,TRUE))
4747
{ tlog(5,"Dealing with ",if(filtered) "" else "un","filtered networks")
4848

49-
# extraction
50-
gg <- cum.graph.extraction(
51-
inter.df=inter.df,
52-
char.stats=char.stats,
53-
scene.chars=scene.chars, scene.stats=scene.stats,
54-
volume.stats=volume.stats,
55-
filtered=filtered,
56-
pub.order=pub.order=="publication",
57-
narr.unit=narr.unit
49+
# extract dynamic graph
50+
gg <- inst.graph.extraction(
51+
inter.df=inter.df,
52+
char.stats=char.stats,
53+
scene.chars=scene.chars, scene.stats=scene.stats,
54+
volume.stats=volume.stats,
55+
filtered=filtered,
56+
pub.order=pub.order=="publication",
57+
narr.unit=narr.unit
5858
)
5959
# record files
60-
cum.write.graph(gs=gg, filtered=filtered, pub.order=pub.order=="publication", char.det=char.det)
61-
#gg <- cum.read.graph(filtered=filtered, remove.isolates=TRUE, pub.order=pub.order=="publication", char.det=char.det, narr.unit=narr.unit)
60+
inst.write.graph(gs=gg, filtered=filtered, pub.order=pub.order=="publication", char.det=char.det)
61+
62+
## extract cumulative dynamic graph
63+
#gg <- cum.graph.extraction(
64+
#inter.df=inter.df,
65+
#char.stats=char.stats,
66+
#scene.chars=scene.chars, scene.stats=scene.stats,
67+
#volume.stats=volume.stats,
68+
#filtered=filtered,
69+
#pub.order=pub.order=="publication",
70+
#narr.unit=narr.unit
71+
#)
72+
## record files
73+
#cum.write.graph(gs=gg, filtered=filtered, pub.order=pub.order=="publication", char.det=char.det)
74+
##gg <- cum.read.graph(filtered=filtered, remove.isolates=TRUE, pub.order=pub.order=="publication", char.det=char.det, narr.unit=narr.unit)
6275
}
6376
}
6477
}

0 commit comments

Comments
 (0)