## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
	collapse = TRUE,
	comment = "#>",
	fig.width = 6,
	fig.height = 4,
	dpi = 96,
	out.width = "100%",
	fig.align = "center"
)

## ----netify-bridge, eval = FALSE----------------------------------------------
# library(netify)
# library(lame)
# 
# netlet <- netify::netify(
# 	edges_long,
# 	actor1 = "from", actor2 = "to", time = "year",
# 	weight = "tie",
# 	dyad_vars = "distance",
# 	output_format = "longit_list",
# 	missing_to_zero = FALSE
# )
# 
# fit <- lame(netlet, family = "binary", R = 2, verbose = FALSE)

## ----quick-start, message=FALSE-----------------------------------------------
library(lame)
set.seed(6886)

# simulate 3 time periods of a 25-node directed network with a
# dyadic similarity covariate driving the ties.
n <- 25; T_periods <- 3
true_intercept <- -1.0
true_beta      <- 0.7

# zero-padded names ("N01" ... "N25") sort the same way alphabetically as
# positionally, so `lame()`'s internal alphabetic actor sort leaves the
# row order unchanged. with non-padded names ("N1", "N10", "N2", ...) the
# stored output comes back in sorted order, not your input order -- the fit
# is still correct as long as Y and the X arrays share the same names.
# explicit dimnames on Y and every covariate array are the safe way to
# guarantee that alignment.
actor_names <- sprintf("N%02d", seq_len(n))

X_list <- lapply(seq_len(T_periods), function(t) {
	# 3-D array [actor x actor x covariate]; the third-dim name becomes
	# the coefficient label downstream
	x <- matrix(rnorm(n * n), n, n)
	array(x, dim = c(n, n, 1),
	      dimnames = list(actor_names, actor_names, "similarity"))
})
Y_list <- lapply(seq_len(T_periods), function(t) {
	# build the linear predictor eta = intercept + beta * X (the same
	# arithmetic a logistic / probit regression would do). then push it
	# through pnorm() -- the standard-normal CDF -- to turn it into a
	# tie probability in [0, 1]. that CDF link is what "probit" means,
	# and it is the link `family = "binary"` uses inside `lame()`. finally,
	# draw a 0/1 tie from a Bernoulli with that probability.
	#
	# the `[, , 1]` peels the first (and only) covariate slice off the
	# 3-D array, leaving an n x n matrix. third-dim index = covariate.
	eta <- true_intercept + true_beta * X_list[[t]][, , 1]
	Y   <- matrix(rbinom(n * n, 1, pnorm(eta)), n, n)
	diag(Y) <- NA   # self-ties are undefined in a unipartite network
	rownames(Y) <- colnames(Y) <- actor_names
	Y
})

# fit a longitudinal AME model with the dyadic similarity covariate.
fit <- lame(
	Y = Y_list,
	Xdyad = X_list,         # one similarity matrix per period
	R = 2,                  # 2D latent space
	family = "binary",      # probit for 0/1 networks
	burn = 20,              # compact burn-in for this example
	nscan = 100,            # compact post-burn-in run for the vignette
	odens = 5,              # thinning
	verbose = FALSE,        # suppress the progress bar / iteration log
	plot = FALSE            # don't pop up live MCMC diagnostic plots during sampling
	                        # `lame()` draws live diagnostics when plot = TRUE;
	                        #  `ame()` accepts plot = for signature parity but ignores it
	                        #  -- the single-period sampler has no live plotting
)

summary(fit)

## ----s3-methods---------------------------------------------------------------
# regression coefficients (posterior means)
coef(fit)

# 95% credible intervals
confint(fit)

# broom-style one-row-per-coefficient frame; ships with lame so it works
# without broom installed and dispatches through broom::tidy(fit) when
# broom is loaded. glance(fit) gives the one-row model summary that
# modelsummary uses for its lower panel. See the overview vignette for
# the full modelsummary / tidybayes / autoplot round-trip.
tidy(fit)

# n_row_actors / n_col_actors are bipartite-only and are NA on a unipartite
# fit like this one. elpd_loo is NA unless the model was fit with
# save_log_lik = TRUE and the loo result cached via fit$loo <- loo(fit).
glance(fit)

# predicted probabilities for every dyad at every time point.
# for a `lame()` fit (panel data), `predict()` returns a *list of length T*,
# where T is the number of time periods. Each element is an n x n matrix of
# posterior-mean predicted tie probabilities (between 0 and 1, since
# family = "binary"). For a single-period `ame()` fit, predict() returns
# the n x n matrix directly, not wrapped in a list.
Y_hat <- predict(fit, type = "response")
length(Y_hat)                      # 3, one matrix per period
dim(Y_hat[[1]])                    # 25 x 25
cat("Predicted probability range:",
		round(range(unlist(Y_hat), na.rm = TRUE), 3), "\n")

# residuals: same list-of-matrices shape, observed minus predicted
resid_list <- residuals(fit)
cat("Residual SD:", round(sd(unlist(resid_list), na.rm = TRUE), 3), "\n")

## ----trace-plot, fig.height=5, fig.alt="MCMC trace and density plots for the regression coefficients (intercept and similarity_dyad); well-mixed traces and unimodal densities indicate adequate convergence."----
trace_plot(fit, params = "beta")

## ----gof-plot, fig.width=8, fig.height=5, fig.alt="Longitudinal goodness-of-fit panels: each facet plots one network statistic (sender/receiver degree heterogeneity, dyadic dependence, triadic dependence, transitivity) across time. The observed series is a solid orange line with points (Okabe-Ito #D55E00); the posterior-predictive median is a dark dashed line; the 95 percent credible interval is a grey ribbon. The dual colour-plus-linetype encoding survives greyscale and colour-blind viewing."----
gof_plot(fit)

## ----uv-plot, fig.width=7, fig.height=6, fig.alt="Circular-layout latent-space plot: each actor's sender position is a triangle and receiver position a circle on concentric rings; actors placed near each other share similar tie patterns."----
uv_plot(fit)

## ----ab-plot, fig.height=4, fig.alt="Lollipop plot of sender effects, sorted by posterior mean; each actor is a stem from zero to its point estimate, with positive values marking unusually active senders. In this null example all stems are short and reflect sampling noise, not planted heterogeneity."----
ab_plot(fit, effect = "sender")

## ----cross-sectional, message=FALSE-------------------------------------------
fit_cs <- ame(
	Y = Y_list[[1]],         # just one time period
	Xdyad = X_list[[1]],     # 3-D array [n, n, 1] with "similarity" slice name
	R = 2,
	family = "binary",
	burn = 20,
	nscan = 100,
	odens = 5,
	verbose = FALSE
)

coef(fit_cs)

## ----dynamic, message=FALSE---------------------------------------------------
fit_dyn <- lame(
	Y = Y_list,
	Xdyad = X_list,
	R = 2,
	dynamic_ab = TRUE,    # time-varying sociality/popularity
	dynamic_uv = TRUE,    # time-varying latent positions
	family = "binary",
	burn = 20,
	nscan = 100,
	odens = 5,
	verbose = FALSE,
	plot = FALSE
)

summary(fit_dyn)

## ----bipartite, message=FALSE-------------------------------------------------
set.seed(42)          # seed the data simulation so the recovery is reproducible
nA <- 15; nB <- 10
row_names <- sprintf("R%02d", seq_len(nA))
col_names <- sprintf("C%02d", seq_len(nB))
X_bip <- lapply(1:3, function(t) {
	x <- matrix(rnorm(nA * nB), nA, nB)
	array(x, dim = c(nA, nB, 1),
	      dimnames = list(row_names, col_names, "similarity"))
})
Y_bip <- lapply(1:3, function(t) {
	eta <- -0.8 + 0.6 * X_bip[[t]][, , 1]
	Y   <- matrix(rbinom(nA * nB, 1, pnorm(eta)), nA, nB)
	rownames(Y) <- row_names; colnames(Y) <- col_names
	Y
})

fit_bip <- lame(
	Y = Y_bip,
	Xdyad = X_bip,
	mode = "bipartite",
	R = 2,
	family = "binary",
	burn = 20, nscan = 100, odens = 5,
	verbose = FALSE, plot = FALSE
)

summary(fit_bip)

## ----checkpoint-demo, eval = FALSE--------------------------------------------
# ck <- tempfile(fileext = ".rds")
# fit1 <- lame(
# 	Y = Y_list, Xdyad = X_list, R = 2, family = "binary",
# 	nscan = 5000, burn = 200, odens = 25,
# 	max_seconds = 30,          # stop after 30 s wall clock
# 	checkpoint_path = ck,      # write state here
# 	verbose = FALSE
# )
# if (isTRUE(fit1$terminated_early)) {
# 	# `nscan` on the resume call = additional stored draws
# 	fit2 <- lame(resume_from = ck, nscan = 2000)
# }

