Skip to contents

Overview

The redeem package models the intensities of interaction formation and dissolution using a log-linear formulation. The intensity (rate) for a dyad \((i,j)\) at time \(t\) is:

\[\lambda_{i,j}(t) = \exp(s_{i,j}(\mathscr{H}_t)^\top \beta + \alpha_i + \alpha_j + f(t, \gamma))\]

where:

  • \(s_{i,j}(\mathscr{H}_t)\) is a vector of sufficient statistics derived from the history of interactions \(\mathscr{H}_t\) up to time \(t\).
  • \(\alpha_i, \alpha_j\) are actor popularity parameters.
  • \(f(t, \gamma) = \sum_{q=1}^Q \gamma_q \mathbb{I}(c_{q-1} \le t < c_q)\) is the baseline step-function representing temporal variation over change points \(0 = c_0 < c_1 < \dots < c_Q\) (with \(\gamma_1 = 0\)).

This vignette provides precise mathematical definitions for all sufficient network statistics implemented in the package, as well as a guide for adding custom statistics.

The complete list of available formula terms is documented in the redeem_terms reference manual page. For model estimation, see the help pages for the models via dem and rem.


Statistic Transformations

Each sufficient statistic \(s_{i,j}(\mathscr{H}_t)\) is defined as a transformed count \(f(N(t))\), where \(N(t)\) is a raw network statistic. The package supports five standard transformations \(f(\cdot)\):

Transformation Mathematical Definition
identity \(f(s) = s\)
log \(f(s) = \log(1 + s)\)
recip \(f(s) = 1/(1+s)\)
bin \(f(s) = \mathbb{I}(s > 0)\)
sig \(f(s) = \frac{s}{s + K}\)

Endogenous Network Statistics

These statistics capture structural dependencies within the network evolution.

1. Intercept (Intercept / intercept)

A constant term representing the baseline log-intensity. \[s_{i,j}(\mathscr{H}_t) = 1\]

2. Inertia (inertia / number_interaction)

Counts how many times the dyad \((i,j)\) has initiated an interaction in the past. \[s_{i,j}(\mathscr{H}_t) = f(N_{i,j}(t))\] where \(N_{i,j}(t) = \sum_{k: t_k < t} \mathbb{I}(i_k = i, j_k = j)\) (or the windowed version \(N_{i,j}^w(t) = \sum_{k: t-w < t_k < t} \mathbb{I}(i_k = i, j_k = j)\)).

3. Reciprocity (reciprocity)

Models the tendency to reciprocate past interactions (directed only). \[s_{i,j}(\mathscr{H}_t) = f(N_{j,i}(t))\]

4. Duration (duration / current_interaction)

Measures the dependency on the time since the current interaction started (DEM only). \[s_{i,j}(\mathscr{H}_t) = \begin{cases} f(t - t_{\text{start, } i,j}) & \text{if dyad } (i,j) \text{ is interacting at } t \\ 0 & \text{otherwise} \end{cases}\] where \(t_{\text{start, } i,j}\) is the timestamp of the last formation event.

5. Participation Shifts (P-shifts)

P-shifts capture the sequential dependencies between consecutive events (REM only). Let the preceding event in the stream be \(A \to B\). For a candidate event \(i \to j\) at time \(t\):

  • psABBA (Reciprocation): \(s_{i,j}(t) = \mathbb{I}(i = B, j = A)\)
  • psABBY (Receiver turn-continuing): \(s_{i,j}(t) = \mathbb{I}(i = B, j \ne A, j \ne B)\)
  • psABAY (Sender turn-continuing): \(s_{i,j}(t) = \mathbb{I}(i = A, j \ne A, j \ne B)\)
  • psABXA (Usurpation to sender): \(s_{i,j}(t) = \mathbb{I}(i \ne A, i \ne B, j = A)\)
  • psABXB (Usurpation to receiver): \(s_{i,j}(t) = \mathbb{I}(i \ne A, i \ne B, j = B)\)
  • psABXY (Completely new dyad): \(s_{i,j}(t) = \mathbb{I}(i \ne A, i \ne B, j \ne A, j \ne B)\)

Triadic Closure and Shared Partners

Triadic statistics capture structural closure. They can be calculated over active edges (designated as current_ in DEM) or historical event existence (designated as general_ in REM/DEM). Let \(\mathcal{A}_t\) represent the set of interacting dyads (for current_) or previously interacted dyads (for general_) at time \(t\).

Common Partners (general_common_partners / current_common_partners)

Counts the number of third-party actors \(k\) sharing a connection of a specified type with both \(i\) and \(j\):

  • OSP (Outgoing Shared Partner): \(s_{i,j}(t) = f(|\{k : (i,k) \in \mathcal{A}_t \land (j,k) \in \mathcal{A}_t\}|)\)
  • ISP (Incoming Shared Partner): \(s_{i,j}(t) = f(|\{k : (k,i) \in \mathcal{A}_t \land (k,j) \in \mathcal{A}_t\}|)\)
  • OTP (Outgoing Two-Path): \(s_{i,j}(t) = f(|\{k : (i,k) \in \mathcal{A}_t \land (k,j) \in \mathcal{A}_t\}|)\)
  • ITP (Incoming Two-Path): \(s_{i,j}(t) = f(|\{k : (k,i) \in \mathcal{A}_t \land (j,k) \in \mathcal{A}_t\}|)\)

Triangles (general_triangle / current_triangle)

Similar to common partners, but only non-zero if the focal dyad itself is active (directed only):

  • OSP Triangle: \(s_{i,j}(t) = \mathbb{I}((i,j) \in \mathcal{A}_t) \times f(|\{k : (i,k) \in \mathcal{A}_t \land (j,k) \in \mathcal{A}_t\}|)\)
  • ISP Triangle: \(s_{i,j}(t) = \mathbb{I}((i,j) \in \mathcal{A}_t) \times f(|\{k : (k,i) \in \mathcal{A}_t \land (k,j) \in \mathcal{A}_t\}|)\)
  • OTP Triangle: \(s_{i,j}(t) = \mathbb{I}((i,j) \in \mathcal{A}_t) \times f(|\{k : (i,k) \in \mathcal{A}_t \land (k,j) \in \mathcal{A}_t\}|)\)
  • ITP Triangle: \(s_{i,j}(t) = \mathbb{I}((i,j) \in \mathcal{A}_t) \times f(|\{k : (k,i) \in \mathcal{A}_t \land (j,k) \in \mathcal{A}_t\}|)\)

Degree and Centrality Statistics

These statistics capture actor-level activity or popularity. Let \(\mathcal{D}_t\) be the network state at time \(t\), and \(d_{i, \text{out}}(t)\), \(d_{i, \text{in}}(t)\) represent the out-degree and in-degree of \(i\) in \(\mathcal{D}_t\). Let \(c_{i, \text{out}}(t)\), \(c_{i, \text{in}}(t)\) be the total out-events and in-events involving \(i\).

Degree Statistics (general_degree_out_sender, etc.)

  • Out-Degree Sender: \(s_{i,j}(t) = f(d_{i, \text{out}}(t))\)
  • Out-Degree Receiver: \(s_{i,j}(t) = f(d_{j, \text{out}}(t))\)
  • In-Degree Sender: \(s_{i,j}(t) = f(d_{i, \text{in}}(t))\)
  • In-Degree Receiver: \(s_{i,j}(t) = f(d_{j, \text{in}}(t))\)
  • Degree Sum (undirected only): \(s_{i,j}(t) = f(d_{i}(t) + d_{j}(t))\)
  • Degree Absolute Difference (undirected only): \(s_{i,j}(t) = f(\|d_{i}(t) - d_{j}(t)\|)\)

Count Statistics (general_count_out_sender, etc.)

Count statistics are identical to degree statistics but use total interaction counts (\(c\)) rather than binary degrees (\(d\)):

  • Out-Count Sender: \(s_{i,j}(t) = f(c_{i, \text{out}}(t))\)
  • Out-Count Receiver: \(s_{i,j}(t) = f(c_{j, \text{out}}(t))\)
  • In-Count Sender: \(s_{i,j}(t) = f(c_{i, \text{in}}(t))\)
  • In-Count Receiver: \(s_{i,j}(t) = f(c_{j, \text{in}}(t))\)
  • Count Sum (undirected only): \(s_{i,j}(t) = f(c_{i}(t) + c_{j}(t))\)
  • Count Absolute Difference (undirected only): \(s_{i,j}(t) = f(\|c_{i}(t) - c_{j}(t)\|)\)

All of these degree and count statistics can be conveniently specified in model formulas using the degree() (or degrees()) and count() helper functions:

  • Out-Degree / Out-Count Sender: degree(type = "out_sender") or count(type = "out_sender")
  • Out-Degree / Out-Count Receiver: degree(type = "out_receiver") or count(type = "out_receiver")
  • In-Degree / In-Count Sender: degree(type = "in_sender") or count(type = "in_sender")
  • In-Degree / In-Count Receiver: degree(type = "in_receiver") or count(type = "in_receiver")
  • Degree / Count Sum: degree(type = "sum") or count(type = "sum")
  • Degree / Count Absolute Difference: degree(type = "absdiff") or count(type = "absdiff")

Exogenous Statistics

1. Dyadic Covariate (dyadic_cov)

\[s_{i,j}(\mathscr{H}_t) = f(X_{i,j}(t))\] where \(X(t)\) is an \(N \times N\) matrix.

2. Monadic Covariate (monadic_cov)

\[s_{i,j}(\mathscr{H}_t) = g(x_i(t), x_j(t))\] where \(x\) is a monadic vector and \(g(\cdot)\) is a user-defined function.


Developer Guide: Adding Custom Sufficient Statistics

To add a new sufficient statistic (e.g., my_stat) to the package, follow this two-step process:

Step 1: Implement the Update Logic in C++

Define a C++ function in the src/ directory (e.g., in a new file or in src/sufficient_statistics.cpp) with the signature ValidateFunction. This function computes the change to the statistic state matrix when an event occurs:

#include "redeem/sufficient_statistics.h"
#include "redeem/extension_api.hpp"

arma::uvec stat_my_stat(
    Data_DEM &object, 
    arma::mat &data, 
    unsigned int &from, 
    unsigned int &to, 
    unsigned int &number_event, 
    unsigned int col_number, 
    std::string transformation, 
    unsigned int type
) {
  if (from == 0 || to == 0) return arma::uvec();
  
  // 1. Calculate which dyad indices are affected and the change value (val)
  double val = (type == 1) ? 1.0 : -1.0; 
  arma::uvec affected_indices = { object.current_stats.find_from_to(from, to) };
  
  // 2. Apply the update using the helper (handles transformations internally)
  apply_update(object, affected_indices, col_number, val, transformation, data);
  
  // 3. Return the indices of modified dyads
  return affected_indices;
}

// 4. Register the term in the global C++ Registry
TERM_REGISTER("my_stat", stat_my_stat);

Step 2: Write the R Initializer Function

In R/init_terms.R, create an initialization function named InitRedeemTerm.my_stat. This function validates user-provided arguments and sets up initial values:

#' @keywords internal
InitRedeemTerm.my_stat <- function(arglist, n_nodes, model_type, directed, ...) {
  # Validate arguments against expected types and models
  arglist <- check.RedeemTerm(arglist,
    model_type = model_type, directed = directed,
    expected = list(transformation = c("identity", "log", "recip", "bin", "sig"), K = "numeric"),
    defaults = list(transformation = "identity", K = 1)
  )
  
  # Define initial statistic values at time 0 (optional)
  eval_at_zero <- matrix(0, n_nodes, n_nodes)
  
  # Return the configuration list
  list(
    base_name = "my_stat",
    transformation = arglist$transformation,
    eval_at_zero = eval_at_zero,
    event_stream = arglist$event_stream
  )
}

Once these steps are completed, run devtools::document() to update namespaces and documentation, and build the package. The new term is now ready for use in dem() and rem() by specifying it directly in the formula (e.g., ~ my_stat(transformation = "log")).