| Title: | Sniffing Emergence and Trajectories in Academic Papers and Patents |
|---|---|
| Description: | Provides a unified set of methods to detect scientific emergence and technological trajectories in academic papers and patents. The package combines citation network analysis with community detection and attribute extraction, also applying natural language processing (NLP) and structural topic modeling (STM) to uncover the contents of research communities. It implements metrics and visualizations of community trajectories, including novelty indicators, citation cycle time, and main path analysis, allowing researchers to map and interpret the dynamics of emerging knowledge fields. Applications of the method include: Souza et al. (2022) <doi:10.1002/bbb.2441>, Souza et al. (2022) <doi:10.14211/ibjesb.e1742>, Matos et al. (2023) <doi:10.1007/s43938-023-00036-3>, Maria et al. (2023) <doi:10.3390/su15020967>, Biazatti et al. (2024) <doi:10.1016/j.envdev.2024.101074>, Felizardo et al. (2025) <doi:10.1007/s12649-025-03136-z>, and Miranda et al. (2025) <doi:10.1016/j.ijhydene.2025.01.089>. |
| Authors: | Roney Fraga Souza [aut, cre, cph] (ORCID: <https://orcid.org/0000-0001-5750-489X>), Luis Felipe de Souza Rodrigues [ctb] |
| Maintainer: | Roney Fraga Souza <[email protected]> |
| License: | GPL-3 |
| Version: | 2.0.0 |
| Built: | 2026-09-30 09:09:55 UTC |
| Source: | https://github.com/roneyfraga/come-to-telegram-rickluminari1--birddog |
Tools to detect emergence and trace technological/scientific trajectories in papers and patents. It reads OpenAlex and Web of Science data, builds citation-based networks, identifies groups, and summarizes their dynamics.
Website: http://roneyfraga.com/birddog/
read_openalex()/read_wos() -> sniff_network() -> sniff_components() ->
sniff_groups_cumulative() -> sniff_groups_lineage() ->
sniff_trajectory_dag() -> sniff_trajectory_braid() -> analysis
(sniff_trajectory_confluence(), _formation(), _destination(),
_contribution(), _self_sufficiency(), _cct(), _entropy(), _hubs(),
_dynamics()), each paired with a plot_* function. Focus an analysis with
subset(flow, ...). "Flow" names the object kind, not an algorithm (stock
vs flow: groups are stock, trajectories are flow): every detector returns a
flow, and alternative detectors (sniff_trajectory_<algo>()) return the
same contract, checked by validate_flow().
cNgN is a group (component N, group N); y<YYYY><cNgN> is a group-year
node; tr::<cNgN> is a central trajectory (reaches the last year, one per
final group); tr1..trN are absorbed trajectories. This grammar is part of
the public API and only changes at major versions.
Trajectories are detected as system-level objects (Dosi, 1982): disjoint chains in the temporal DAG of cumulative clusterings. A trajectory that stops being detected loses its identity, not its papers; tracking its terminal cohort to the last year classifies the outcome as convergence (one destination), divergence (many), or dormancy (none) — with emergence indicators on the living chains (Rotolo et al., 2015; Carley et al., 2017).
Maintainer: Roney Fraga Souza [email protected] (ORCID) [copyright holder]
Other contributors:
Luis Felipe de Souza Rodrigues [email protected] [contributor]
Useful links:
Report bugs at https://github.com/roneyfraga/birddog/issues
Derives a fixed_state_thresholds()-shaped list from an observed
sniff_trajectory_dynamics() table, so the cut points adapt to the dataset
instead of fixed constants. The growth cuts are placed k robust deviations
(median absolute deviation) from the median, which resists outliers such as a
single fast-growing young core; the bounded metrics use their median as the
neutral split, and dormancy_share keeps the absolute majority rule (its
distribution is usually too concentrated at zero to standardize).
data_state_thresholds(dyn, k = 1)data_state_thresholds(dyn, k = 1)
dyn |
A |
k |
Number of robust deviations (scaled MAD) above/below the median for the
growth cuts (default 1): |
Use it as a drop-in for the thresholds argument; the metrics are
threshold-independent, so one pass is enough to derive and re-classify:
dyn <- sniff_trajectory_dynamics(flow),
th <- data_state_thresholds(dyn),
dyn <- sniff_trajectory_dynamics(flow, thresholds = th).
A list shaped like fixed_state_thresholds():
emergence_growth, decline_growth (robust, from the centrals' growth),
emergence_novelty (median novelty of the centrals), convergence_entropy
(median destination entropy of the absorbed), and dormancy_share (the
default majority rule). Any quantity that cannot be derived (too few points,
zero spread) falls back to its fixed_state_thresholds() value.
fixed_state_thresholds(), sniff_trajectory_dynamics(),
plot_trajectory_dynamics()
The fixed knobs for sniff_trajectory_dynamics(). These are not the applied
default: by default the cut points are derived from the data via
data_state_thresholds(), and these constants serve as the explicit
fixed-constant option (pass this list as thresholds) and as the fallback for
any quantity that cannot be derived. The classification follows two axes (see
sniff_trajectory_dynamics()): a life-cycle phase for living (central)
trajectories and a terminal fate for declining (absorbed) ones.
fixed_state_thresholds()fixed_state_thresholds()
A list:
emergence_growth (min annualized growth_rate for a central to count as
emergence; default 0.15), emergence_novelty (min novelty for emergence;
default 0.30), decline_growth (a central at or below this growth_rate is
dormant by stall; default -0.05), convergence_entropy (an absorbed
trajectory whose normalized destination entropy is below this converged, else
diverged; default 0.5), dormancy_share (an absorbed trajectory whose
terminal cohort drops at least this share is dormant by death; default 0.5).
Retrieves specified fields for OpenAlex work IDs using the OpenAlex API. Processes data in batches to avoid API rate limits.
get_openalex_fields( openalex_ids, variables = "publication_year", batch_size = 50, save_dir = NULL )get_openalex_fields( openalex_ids, variables = "publication_year", batch_size = 50, save_dir = NULL )
openalex_ids |
Character vector of OpenAlex work IDs (format: "W1234567890") or a data frame/tibble containing a column named "CR" with OpenAlex IDs. IDs can be semicolon-separated strings which will be split automatically. |
variables |
Character vector of variable names to fetch from OpenAlex. Options include: "publication_year", "doi", "type", "source_display_name", or any valid OpenAlex work field. Default is "publication_year". |
batch_size |
Number of IDs to process per API call (default: 50). Smaller batches help avoid API rate limits. |
save_dir |
Optional path to directory where intermediate results should be saved as RDS files. If NULL (default), no saving occurs. Directory will be created if it doesn't exist. |
This function:
Accepts either a character vector of IDs or a data frame with a "CR" column
Splits semicolon-separated ID strings into individual IDs
Validates IDs against the pattern "^W\d+$"
Fetches specified variables from OpenAlex API in batches
Optionally saves each batch to disk as it's processed
Handles API errors gracefully with informative messages
Includes delays between batches to respect API rate limits
A tibble with the following columns:
id: The OpenAlex work ID
One column for each requested variable (e.g., "publication_year", "doi", "type")
Rows without valid OpenAlex IDs or where API calls fail will have NA values.
The OpenAlex API has rate limits. This function implements:
Batch processing to reduce number of API calls
0.5 second delays between batches
Error handling for failed API requests
Progress messages to track execution
Optional disk saving for data persistence
If you encounter rate limiting errors, consider reducing batch_size or implementing longer delays.
## Not run: # From a character vector ids <- c("W2261389918", "W1548650423", "W1504492735") result <- get_openalex_fields(ids) # Fetch multiple variables result <- get_openalex_fields( ids, variables = c("publication_year", "doi", "type") ) # From a data frame with CR column oa_data <- data.frame(CR = c("W123;W456", "W789")) result <- get_openalex_fields(oa_data) # Save intermediate results while downloading result <- get_openalex_fields( ids, variables = c("publication_year", "source_display_name"), save_dir = tempdir() ) ## End(Not run)## Not run: # From a character vector ids <- c("W2261389918", "W1548650423", "W1504492735") result <- get_openalex_fields(ids) # Fetch multiple variables result <- get_openalex_fields( ids, variables = c("publication_year", "doi", "type") ) # From a data frame with CR column oa_data <- data.frame(CR = c("W123;W456", "W789")) result <- get_openalex_fields(oa_data) # Save intermediate results while downloading result <- get_openalex_fields( ids, variables = c("publication_year", "source_display_name"), save_dir = tempdir() ) ## End(Not run)
"Flow" names the object kind, not the algorithm (stock vs flow: groups are
stock, trajectories are flow). Every trajectory detector returns a flow –
the temporal decomposition of the DAG into trajectories plus the confluence
tree – so this predicate accepts the result of sniff_trajectory_braid()
and of any future sniff_trajectory_<algo>() detector. is_flow() is the
cheap predicate; validate_flow() is the contract's authoritative checker.
is_flow(x)is_flow(x)
x |
Any object. |
A length-one logical.
sniff_trajectory_braid(), validate_flow()
Other trajectory detection:
sniff_trajectory_braid(),
sniff_trajectory_channel(),
sniff_trajectory_dag(),
subset.birddog_flow(),
validate_flow()
Sort character vectors containing embedded numbers in natural numeric
order rather than lexicographic order. For example, "c1g2" comes before
"c1g10", which standard sort() would not guarantee.
mixed_sort(x)mixed_sort(x)
x |
Character vector to sort. |
The function extracts all numeric segments from each string using
stringr::str_extract_all() and sorts by them sequentially (first
number, then second). Designed for two-segment identifiers such as
"c1g1", "c2g10", etc.
A character vector sorted in natural numeric order.
sort() for standard lexicographic sorting.
mixed_sort(c("c1g10", "c1g2", "c1g1", "c1g9")) mixed_sort(c("group12", "group1", "group3"))mixed_sort(c("c1g10", "c1g2", "c1g1", "c1g9")) mixed_sort(c("group12", "group1", "group3"))
Experimental. The directed counterpart of plot_trajectory_confluence_matrix():
a heatmap of the cross-citation channels from a sniff_groups_influence()
object. Rows are the citing group ("is influenced by") and columns the
cited group ("the influencer"), so a cell and its mirror across the diagonal
are the two directions of one pair. Confluence is symmetric; influence is not,
and that broken symmetry is the bidirectional relationship.
plot_groups_influence_matrix( influence, fill = c("surprise", "raw", "debt", "audience", "salton"), diagonal = c("mute", "show", "drop"), show_values = TRUE, label_size = 4, order_by = c("group", "balance"), axis_text_size = NULL, axis_title_size = NULL, x_angle = 0, legend_position = "right", bg = "white", title = NULL )plot_groups_influence_matrix( influence, fill = c("surprise", "raw", "debt", "audience", "salton"), diagonal = c("mute", "show", "drop"), show_values = TRUE, label_size = 4, order_by = c("group", "balance"), axis_text_size = NULL, axis_title_size = NULL, x_angle = 0, legend_position = "right", bg = "white", title = NULL )
influence |
A |
fill |
Which value to colour by: |
diagonal |
How to treat the intra-group cohesion cells: |
show_values |
Print the value inside each cell (default |
label_size |
Font size of the in-cell values (default |
order_by |
Group order on both axes: |
axis_text_size, axis_title_size
|
Font sizes of the tick labels and axis
titles; |
x_angle |
Rotation in degrees of the column (cited-group) labels on the
top axis. Default |
legend_position |
Legend placement: |
bg |
Panel/plot background colour (default |
title |
Plot title; |
A ggplot object.
sniff_groups_influence(), plot_groups_influence_network(),
plot_trajectory_confluence_matrix()
Other visualization:
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: infl <- sniff_groups_influence(groups) plot_groups_influence_matrix(infl) plot_groups_influence_matrix(infl, fill = "debt", diagonal = "drop") ## End(Not run)## Not run: infl <- sniff_groups_influence(groups) plot_groups_influence_matrix(infl) plot_groups_influence_matrix(infl, fill = "debt", diagonal = "drop") ## End(Not run)
Experimental. Draws the directed influence backbone from a
sniff_groups_influence() object as a node-link graph: an arrow runs from the
source (the cited, more foundational group) to the recipient (the
citing group). By default the edges are the net flow $net (), the "who, on balance, leads whom" spine; nodes are coloured
by role (source / broker / sink) and sized by citation activity.
plot_groups_influence_network( influence, weight = c("net", "gross", "surprise"), min_weight = 0, node_size = c("equal", "io", "balance"), colour_role = TRUE, edge_labels = TRUE, edge_digits = NULL, labels = TRUE, label_size = NULL, layout = "sugiyama", title = NULL )plot_groups_influence_network( influence, weight = c("net", "gross", "surprise"), min_weight = 0, node_size = c("equal", "io", "balance"), colour_role = TRUE, edge_labels = TRUE, edge_digits = NULL, labels = TRUE, label_size = NULL, layout = "sugiyama", title = NULL )
influence |
A |
weight |
Which edges and weights to draw: |
min_weight |
Drop edges whose weight is below this (default |
node_size |
How to size the group nodes: |
colour_role |
Colour nodes by their source / broker / sink role
(default |
edge_labels |
Print the weight on each edge (default |
edge_digits |
Decimal places for the edge weight labels; |
labels |
Print the group label on each node (default |
label_size |
Font size of the node labels; |
layout |
A |
title |
Plot title; |
A ggplot object.
sniff_groups_influence(), plot_groups_influence_matrix()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: infl <- sniff_groups_influence(groups) plot_groups_influence_network(infl) plot_groups_influence_network(infl, weight = "gross", min_weight = 50) ## End(Not run)## Not run: infl <- sniff_groups_influence(groups) plot_groups_influence_network(infl) plot_groups_influence_network(infl, weight = "gross", min_weight = 50) ## End(Not run)
Creates a 2D visualization of technological trajectories based on group similarity metrics, showing the evolution of research groups over time with node size representing group importance and color representing publication-year deviation.
plot_groups_lineage_2d( groups_lineage, group = "c1g1", jaccard_similarity = 0.01, prop_tracked_intra_group_treshold = 0.2, label_type = "size", label_vertical_position = 0, label_horizontal_position = 0, label_angle = 0, time_span = NA, show_legend = TRUE, color_by = c("py_deviation", "final_group_share") )plot_groups_lineage_2d( groups_lineage, group = "c1g1", jaccard_similarity = 0.01, prop_tracked_intra_group_treshold = 0.2, label_type = "size", label_vertical_position = 0, label_horizontal_position = 0, label_angle = 0, time_span = NA, show_legend = TRUE, color_by = c("py_deviation", "final_group_share") )
groups_lineage |
A list with components
|
group |
The specific group to visualize (default: "c1g1"). |
jaccard_similarity |
Minimum Jaccard similarity threshold for connections (default: 0.1). |
prop_tracked_intra_group_treshold |
Minimum proportion of tracked intra-group documents for nodes to be included (default: 0.2). |
label_type |
Type of labels to display on nodes ("size" for weighted size or "id" for group IDs). |
label_vertical_position |
Kept for backward compatibility; no longer applied, as labels are auto-positioned (repelled) with a white halo so they stay readable over dark nodes and do not overlap. |
label_horizontal_position |
Kept for backward compatibility; not applied
(see |
label_angle |
Kept for backward compatibility; not applied (labels are auto-positioned). |
time_span |
Optional vector of years to display; if |
show_legend |
Logical indicating whether to show the color legend (default: |
color_by |
What the node color encodes: |
A descriptive stock view of one group's lineage network. A group's lineage
can share year communities with other groups (soft membership), so this view
makes no trajectory claims – for detection see sniff_trajectory_braid().
A ggplot2 object visualizing the technological trajectories.
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: # Compute trajectories first traj_data <- sniff_groups_lineage(groups_cumulative) # Visualize a specific group (pass the whole object; the function extracts what it needs internally) plot_groups_lineage_2d( groups_lineage = traj_data, group = "c1g5", jaccard_similarity = 0.3 ) ## End(Not run)## Not run: # Compute trajectories first traj_data <- sniff_groups_lineage(groups_cumulative) # Visualize a specific group (pass the whole object; the function extracts what it needs internally) plot_groups_lineage_2d( groups_lineage = traj_data, group = "c1g5", jaccard_similarity = 0.3 ) ## End(Not run)
Creates an interactive 3D visualization of technological trajectories showing the evolution of research groups over time with node size representing group importance and color representing publication year deviation.
plot_groups_lineage_3d( groups_lineage, group = "component1_g01", jaccard_similarity = 0.1, prop_tracked_intra_group_treshold = 0.2, label_type = "size", label_vertical_position = 0, label_horizontal_position = 0, label_angle = 0, time_span = NA, show_legend = TRUE, last_year_keywords = NULL, log_scale = FALSE )plot_groups_lineage_3d( groups_lineage, group = "component1_g01", jaccard_similarity = 0.1, prop_tracked_intra_group_treshold = 0.2, label_type = "size", label_vertical_position = 0, label_horizontal_position = 0, label_angle = 0, time_span = NA, show_legend = TRUE, last_year_keywords = NULL, log_scale = FALSE )
groups_lineage |
A list containing two components:
|
group |
The specific group to visualize (default: "component1_g01") |
jaccard_similarity |
Minimum Jaccard similarity threshold for connections (default: 0.1) |
prop_tracked_intra_group_treshold |
Minimum proportion of tracked intra-group documents for nodes to be included (default: 0.2) |
label_type |
Type of labels to display on nodes ("size" for weighted size or "id" for group IDs) |
label_vertical_position |
Vertical adjustment for node labels (default: 0) |
label_horizontal_position |
Horizontal adjustment for node labels (default: 0) |
label_angle |
Angle for node labels (default: 0) |
time_span |
Optional vector specifying the time span to display (default: NA shows all years) |
show_legend |
Logical indicating whether to show the color legend (default: TRUE) |
last_year_keywords |
Optional keywords description for the last year (default: NULL) |
log_scale |
Whether to apply log transformation to the z-axis size values
(default: FALSE). Uses |
A descriptive stock view of one group's lineage network. A group's lineage
can share year communities with other groups (soft membership), so this view
makes no trajectory claims – for detection see sniff_trajectory_braid().
A plotly 3D visualization object
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: # First get trajectory data traj_data <- sniff_groups_lineage(groups_cumulative) # Visualize a specific group in 3D plot_groups_lineage_3d( groups_lineage = traj_data, group = "component1_g05", jaccard_similarity = 0.2 ) ## End(Not run)## Not run: # First get trajectory data traj_data <- sniff_groups_lineage(groups_cumulative) # Visualize a specific group in 3D plot_groups_lineage_3d( groups_lineage = traj_data, group = "component1_g05", jaccard_similarity = 0.2 ) ## End(Not run)
The birddog stock map: every paper in the cumulative network is a point, laid out by a force-directed layout of the citation network and coloured by its final group, with a text label at each group's centroid. It is the stock snapshot – all final groups, the whole network at once – positioned by citation structure rather than text embeddings.
plot_groups_map( groups_cumulative, labels_text = NULL, network_until = NULL, layout = "drl", seed = 888L, point_size = 0.9, point_alpha = 0.7, show_unassigned = TRUE, label = TRUE, label_size = 3.2, title = "Group map", title_size = 14, normalize = FALSE, normalize_q = 0.99 )plot_groups_map( groups_cumulative, labels_text = NULL, network_until = NULL, layout = "drl", seed = 888L, point_size = 0.9, point_alpha = 0.7, show_unassigned = TRUE, label = TRUE, label_size = 3.2, title = "Group map", title_size = 14, normalize = FALSE, normalize_q = 0.99 )
groups_cumulative |
A stock object from |
labels_text |
Optional |
network_until |
Snapshot year to map. |
layout |
Force-directed layout passed to |
seed |
Integer seed for the stochastic layout (default |
point_size |
Node point size (default |
point_alpha |
Node point alpha (default |
show_unassigned |
Draw nodes with no group ( |
label |
Draw the group text labels at centroids (default |
label_size |
Font size of the centroid labels (default |
title |
Plot title: the default |
title_size |
Title font size (default |
normalize |
Rescale the layout for cross-year comparison (default
|
normalize_q |
Quantile of the radial distance kept inside the normalised
window (default |
A ggplot object.
sniff_groups_cumulative(), plot_trajectory_dag()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
One layout of the final network, with every paper revealed at the year it
first enters the cumulative network and held at its final position, so the
cloud grows without nodes moving. Returns a gganim object; render it to a
GIF/MP4 with gganimate::animate() and gganimate::anim_save(). Reveal and
colour reuse the static plot_groups_map() engine.
plot_groups_map_animation( groups_cumulative, labels_text = NULL, layout = "drl", seed = 888L, show_unassigned = TRUE, label = TRUE, label_size = 3.2, title_size = 14, point_size = 0.9 )plot_groups_map_animation( groups_cumulative, labels_text = NULL, layout = "drl", seed = 888L, show_unassigned = TRUE, label = TRUE, label_size = 3.2, title_size = 14, point_size = 0.9 )
groups_cumulative |
A stock object from |
labels_text |
Optional |
layout |
Force-directed layout passed to |
seed |
Integer seed for the stochastic layout (default |
show_unassigned |
Reveal nodes with no group ( |
label |
Draw persistent group labels at the final centroids (default |
label_size |
Font size of the centroid labels (default |
title_size |
Font size of the per-frame |
point_size |
Node point size (default |
Rendering: the transition is one frame per year. Render with duration (not
fps), which works with either renderer: gganimate::animate(anim, duration = 8)
gives nframes/duration fps (with the magick renderer that ratio must divide
100, e.g. 32 years / 8 = 4). Save the rendered object, not the gganim:
a <- gganimate::animate(anim, duration = 8); gganimate::anim_save("x.gif", a).
Installing gifski removes the fps constraint.
A gganim object (render with gganimate::animate()).
plot_groups_map(), plot_groups_map_interactive()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
The interactive twin of plot_groups_map(). Every paper is a scattergl
point laid out by a force-directed layout of the citation network and coloured
by its final group; hovering a node shows its title, year, authors, keywords,
and final group. Returns a plotly htmlwidget – it renders in the RStudio
Viewer, embeds in Quarto/Shiny, or saves to a standalone file with
htmlwidgets::saveWidget().
plot_groups_map_interactive( groups_cumulative, labels_text = NULL, network_until = NULL, layout = "drl", seed = 888L, show_unassigned = TRUE, label = TRUE, label_size = 12, title = "Group map", title_size = 16, normalize = FALSE, normalize_q = 0.99, marker_size = 5 )plot_groups_map_interactive( groups_cumulative, labels_text = NULL, network_until = NULL, layout = "drl", seed = 888L, show_unassigned = TRUE, label = TRUE, label_size = 12, title = "Group map", title_size = 16, normalize = FALSE, normalize_q = 0.99, marker_size = 5 )
groups_cumulative |
A stock object from |
labels_text |
Optional |
network_until |
Snapshot year. |
layout |
Force-directed layout passed to |
seed |
Integer seed for the stochastic layout (default |
show_unassigned |
Draw nodes with no group ( |
label |
Draw group labels at centroids as annotations (default |
label_size |
Font size (px) of the centroid label annotations (default |
title |
Plot title: the default |
title_size |
Title font size in px (default |
normalize |
Rescale to a fixed unit window for cross-year comparison
(default |
normalize_q |
Quantile of the radial distance kept inside the normalised
window (default |
marker_size |
Node marker size (default |
A plotly htmlwidget.
plot_groups_map(), sniff_groups_cumulative()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
The stock-side bridge view. One column per year; within a column the
cluster-year nodes are ranked by paper count with the largest at the baseline,
dot area encodes paper count, and (optionally) the number of groups that year
is printed atop the column. No succession edges, no birth rings – it shows the
annual + cumulative clustering fan settling onto the final-year groups. It is
the stock-side counterpart of the flow-side plot_trajectory_dag(), and reads
the sniff_trajectory_dag() object directly.
plot_groups_per_year( dag, color_terminal = FALSE, point_size = 6, show_count = TRUE, count_size = 3.2, count_colour = "white", count_fill = "grey45", label_terminal = TRUE, label_size = 3, axis_text_size = NULL, title = "Groups per year" )plot_groups_per_year( dag, color_terminal = FALSE, point_size = 6, show_count = TRUE, count_size = 3.2, count_colour = "white", count_fill = "grey45", label_terminal = TRUE, label_size = 3, axis_text_size = NULL, title = "Groups per year" )
dag |
A |
color_terminal |
Colour the dots by |
point_size |
Upper bound of the dot-size range; paper count is mapped to
|
show_count |
Print the number of groups atop each year column, on a
filled label for emphasis (default |
count_size |
Font size of the per-year group-count text (default |
count_colour |
Font colour of the count text (default |
count_fill |
Background colour of the count label (default |
label_terminal |
Label the final-year nodes with their group id ( |
label_size |
Font size of the final-node labels (default |
axis_text_size |
Font size of the x-axis (year) tick labels; |
title |
Plot title (default |
The visual encoding:
One column per year along the x-axis; the y-axis has no intrinsic meaning (nodes are stacked by rank).
Within a column the cluster-year nodes are ranked by paper count with the largest at the baseline, and dot area encodes the paper count.
With show_count = TRUE the number of groups that year is printed atop
the column on a filled label (count_fill / count_colour).
There are no succession edges and no births: this is the stock-side view
of how many groups exist each year and how large they are, settling onto the
final-year groups. For the flow (edges, trajectories) use
plot_trajectory_dag().
With label_terminal = TRUE the final-year nodes are labelled c1gN to the
right of the last column.
A ggplot object.
sniff_trajectory_dag(), plot_trajectory_dag(),
plot_trajectory_dag_interactive()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: # docs_per_group comes from sniff_groups_lineage() dag <- sniff_trajectory_dag(docs_per_group) plot_groups_per_year(dag) # colour the dots by destination, bigger count chips and year labels plot_groups_per_year(dag, color_terminal = TRUE, count_size = 5, axis_text_size = 14) ## End(Not run)## Not run: # docs_per_group comes from sniff_groups_lineage() dag <- sniff_trajectory_dag(docs_per_group) plot_groups_per_year(dag) # colour the dots by destination, bigger count chips and year labels plot_groups_per_year(dag, color_terminal = TRUE, count_size = 5, axis_text_size = 14) ## End(Not run)
A braided river system over sniff_trajectory_confluence(): every trajectory is
a stream flowing left to right along the publication year, merging into its
absorber where it hands off, until the central trajectories (tr::cNgN) reach
the final year. Each tributary is drawn grey until the first paper it
transfers is published, then in its colour; at the merge a band of width equal
to the transferred papers (n of the tributary's cohort_size) bends into
the river, which widens by that amount. The central spine is centered on its
band with tributaries packed above and below. With target = NULL the whole
system is drawn (one colour per destination central); with target = "tr::c1g1"
only that central's direct feeders are drawn (one colour per tributary).
plot_trajectory_confluence( conf, target = "all", depth = NULL, min_n = 5, min_prop = 0.05, min_total_size = 0, min_duration_years = 0, width_range = c(0, 0.42), label_terminal = c("id_count", "id"), label_intermediary = c("id_count", "id"), label_terminal_size = 4.5, label_intermediary_size = 4, labels_text = NULL, legend_ncol = NULL, palette = NULL, axis_text_size = NULL, year_range = NULL, show_secondary = TRUE, secondary_min_prop = 0.2, smooth = TRUE, title = NULL )plot_trajectory_confluence( conf, target = "all", depth = NULL, min_n = 5, min_prop = 0.05, min_total_size = 0, min_duration_years = 0, width_range = c(0, 0.42), label_terminal = c("id_count", "id"), label_intermediary = c("id_count", "id"), label_terminal_size = 4.5, label_intermediary_size = 4, labels_text = NULL, legend_ncol = NULL, palette = NULL, axis_text_size = NULL, year_range = NULL, show_secondary = TRUE, secondary_min_prop = 0.2, smooth = TRUE, title = NULL )
conf |
A |
target |
What to draw. |
depth |
How many confluence levels upstream of the roots to draw; |
min_n |
Minimum papers a tributary must transfer to its absorber to be
counted; below this it is pruned from the drawing and the river width,
together with its own upstream subtree (default |
min_prop |
Minimum share of a tributary's own cohort that must transfer
(default |
min_total_size |
Minimum total size of a tributary (its own distinct
paper count across all years) for it to be drawn (default |
min_duration_years |
Minimum lifespan in years of a tributary (its
last year minus its first year, plus one) for it to be drawn (default |
width_range |
Half-width range |
label_terminal |
Label content for the final (terminal) trajectories:
|
label_intermediary |
Label content for the intermediate (tributary)
trajectories: |
label_terminal_size |
Text size of the final (terminal) labels
(default |
label_intermediary_size |
Text size of the intermediate (tributary)
labels (default |
labels_text |
Optional |
legend_ncol |
Number of columns in the |
palette |
Optional named colour vector overriding the default hues (keyed
by destination central in the all/multi view, else by tributary |
axis_text_size |
Font size of the x-axis (year) tick labels; |
year_range |
Optional |
show_secondary |
Draw faint links from a tributary to the other finals
its terminal cohort fed, beyond the dominant one it merges into (default
|
secondary_min_prop |
Minimum share of a tributary's terminal cohort that
must reach a secondary final for its link to be drawn (default |
smooth |
Gaussian-smooth the stream outlines (default |
title |
Plot title; |
x = publication year (never reassigned); the y-axis has no intrinsic meaning, it only packs streams to minimise merge crossings.
Tributary width follows its community size each year; grey before the first transferred paper, then its colour. Central spine width is the smoothed cumulative inflow of its counted tributaries (papers transferred), on one proportional scale shared with the tributaries (0 papers give 0 width).
A tributary hands n of its cohort_size papers over; only n enters the
river, so the merge band keeps a constant width equal to the transferred
papers (the n/cohort label makes the split explicit) and bends over a span
that scales with the distance travelled, so its thickness reads the same for
near and far feeders.
min_n / min_prop decide what is counted, not merely hidden: dropped
tributaries leave the river narrower and are summarised in the caption.
An intermediate that delivers 0 documents to the final it flows into (its terminal cohort lands entirely outside that final's community) is always excluded, together with its upstream subtree, regardless of the thresholds: a stream contributing nothing to the destination cannot appear.
A ggplot object.
sniff_trajectory_confluence(), plot_trajectory_formation(),
sniff_trajectory_braid()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: flow <- sniff_trajectory_braid(docs_per_group) conf <- sniff_trajectory_confluence(flow) # the whole system: how every central forms plot_trajectory_confluence(conf, min_n = 20) # one central's direct feeders, coloured per tributary plot_trajectory_confluence(conf, target = "tr::c1g1") # focus on one intermediate trajectory: its feeders + the finals it fed plot_trajectory_confluence(conf, target = "tr7") # two selected centrals, a description legend, bigger year labels, bare ids, # a visible floor on thin streams, and only sizeable tributaries descr <- data.frame(id = c("c1g1", "c1g5"), text = c("Topic A", "Topic B")) plot_trajectory_confluence(conf, target = c("tr::c1g1", "tr::c1g5"), labels_text = descr, axis_text_size = 14, label_intermediary = "id", min_total_size = 30, min_duration_years = 3, width_range = c(0.05, 0.45)) ## End(Not run)## Not run: flow <- sniff_trajectory_braid(docs_per_group) conf <- sniff_trajectory_confluence(flow) # the whole system: how every central forms plot_trajectory_confluence(conf, min_n = 20) # one central's direct feeders, coloured per tributary plot_trajectory_confluence(conf, target = "tr::c1g1") # focus on one intermediate trajectory: its feeders + the finals it fed plot_trajectory_confluence(conf, target = "tr7") # two selected centrals, a description legend, bigger year labels, bare ids, # a visible floor on thin streams, and only sizeable tributaries descr <- data.frame(id = c("c1g1", "c1g5"), text = c("Topic A", "Topic B")) plot_trajectory_confluence(conf, target = c("tr::c1g1", "tr::c1g5"), labels_text = descr, axis_text_size = 14, label_intermediary = "id", min_total_size = 30, min_duration_years = 3, width_range = c(0.05, 0.45)) ## End(Not run)
The plotly twin of plot_trajectory_confluence(). Every trajectory is a filled
stream over the publication year, laid out by the same centered lane packing and
coloured by the same rule (per destination central in the whole-forest / multi
view, per tributary in a single-central view). There is no legend: hovering a
central river shows tr::cNgN (size) (plus its labels_text description),
and hovering a tributary shows trN (n/cohort -> tr::cNgN). With
label_terminal = TRUE the central (or selected) rivers are also named in a
de-overlapped column on the right, joined to their stream by a leader line.
Returns a plotly htmlwidget.
plot_trajectory_confluence_interactive( conf, target = "all", depth = NULL, min_n = 5, min_prop = 0.05, min_total_size = 0, min_duration_years = 0, width_range = c(0, 0.42), palette = NULL, labels_text = NULL, label_terminal = TRUE, label_size = 12, smooth = TRUE, title = NULL )plot_trajectory_confluence_interactive( conf, target = "all", depth = NULL, min_n = 5, min_prop = 0.05, min_total_size = 0, min_duration_years = 0, width_range = c(0, 0.42), palette = NULL, labels_text = NULL, label_terminal = TRUE, label_size = 12, smooth = TRUE, title = NULL )
conf |
A |
target |
What to draw, as in |
depth, min_n, min_prop, min_total_size, min_duration_years, width_range, smooth
|
Passed through to the same pruning / geometry helpers as
|
palette |
Optional named colour vector overriding the default hues (keyed by
destination central in the all/multi view, else by tributary |
labels_text |
Optional |
label_terminal |
Name the central (or selected) rivers in a de-overlapped
column to the right, each joined to its stream by a leader line (default
|
label_size |
Font size (px) of those right-hand labels (default |
title |
Plot title; |
Unlike the static function this covers the forest views only ("all",
"finals", "intermediary", one or more centrals, and an intermediate shown as
its upstream feeders); the single-intermediate focus view and the faint
secondary-destination links are not drawn.
Built with plotly, following the same principles as
plot_trajectory_dag_interactive():
The x-axis is the publication year; the y-axis has no intrinsic meaning (it only packs streams to minimise merge crossings).
Each stream is a filled polygon (fill = "toself", hoverable over its area);
a tributary is grey before the first paper it transfers, then in its colour.
There is no legend: detail is revealed on hover, and only the terminal (central or selected) rivers carry a printed label.
An intermediate that delivers 0 documents to the final it flows into is
always excluded (as in plot_trajectory_confluence()).
The widget renders in the RStudio Viewer, embeds in Quarto/Shiny, or saves to a
standalone file with htmlwidgets::saveWidget().
A plotly htmlwidget.
plot_trajectory_confluence(), plot_trajectory_dag_interactive(),
sniff_trajectory_confluence()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: flow <- sniff_trajectory_braid(docs_per_group) conf <- sniff_trajectory_confluence(flow) plot_trajectory_confluence_interactive(conf, min_n = 20) # one central's formation, hover the tributaries for their n/cohort plot_trajectory_confluence_interactive(conf, target = "tr::c1g1") ## End(Not run)## Not run: flow <- sniff_trajectory_braid(docs_per_group) conf <- sniff_trajectory_confluence(flow) plot_trajectory_confluence_interactive(conf, min_n = 20) # one central's formation, hover the tributaries for their n/cohort plot_trajectory_confluence_interactive(conf, target = "tr::c1g1") ## End(Not run)
A heatmap of how each intermediate (absorbed) trajectory's papers spread across
the final trajectories. By default rows are the final trajectories tr::cNgN
and columns are the intermediate trajectories trN (swap with
orientation); the fill is the coupling strength (shared documents) and each
intermediate's dominant-final cell is outlined. Which intermediates appear is
decided by the same pruning the other flow plots use
(plot_trajectory_lines_2d(), plot_trajectory_confluence()), so the column
set is consistent across views.
plot_trajectory_confluence_matrix( flow, conf = NULL, fill = c("n_shared", "prop_of_group", "prop_of_traj"), orientation = c("finals-cols", "finals-rows"), order_by = c("terminal", "reach", "size", "alpha", "cluster"), min_n = 5, min_prop = 0.05, min_total_size = 0, min_duration_years = 0, min_target_n = 0, mark_terminal = TRUE, show_values = FALSE, label_size = 2.5, axis_text_size = NULL, axis_title_size = NULL, legend_position = "right", legend_text_size = NULL, bg = "white", title = "Trajectory matrix" )plot_trajectory_confluence_matrix( flow, conf = NULL, fill = c("n_shared", "prop_of_group", "prop_of_traj"), orientation = c("finals-cols", "finals-rows"), order_by = c("terminal", "reach", "size", "alpha", "cluster"), min_n = 5, min_prop = 0.05, min_total_size = 0, min_duration_years = 0, min_target_n = 0, mark_terminal = TRUE, show_values = FALSE, label_size = 2.5, axis_text_size = NULL, axis_title_size = NULL, legend_position = "right", legend_text_size = NULL, bg = "white", title = "Trajectory matrix" )
flow |
A |
conf |
An optional precomputed |
fill |
|
orientation |
|
order_by |
How the intermediate trajectories are ordered along their axis:
|
min_n, min_prop, min_total_size, min_duration_years
|
Thresholds selecting
which intermediate trajectories are shown, passed to the same prune as
|
min_target_n |
Minimum shared documents a cell must have to be drawn
(default |
mark_terminal |
Outline each intermediate's dominant-final cell
(default |
show_values |
Print the value inside each cell (default |
label_size |
Font size of the in-cell values (default |
axis_text_size |
Font size of the axis (trajectory id) tick labels; |
axis_title_size |
Font size of the axis titles ("Final Trajectory" /
"Intermediate Trajectory"); |
legend_position |
Where to put the colourbar: |
legend_text_size |
Font size of the legend text; |
bg |
Panel/plot background colour (default |
title |
Plot title (default |
A ggplot object.
sniff_trajectory_braid(), plot_trajectory_lines_2d(),
plot_trajectory_confluence()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: flow <- sniff_trajectory_braid(docs_per_group) plot_trajectory_confluence_matrix(flow, min_n = 20, show_values = TRUE) plot_trajectory_confluence_matrix(flow, order_by = "cluster", bg = "grey20") ## End(Not run)## Not run: flow <- sniff_trajectory_braid(docs_per_group) plot_trajectory_confluence_matrix(flow, min_n = 20, show_values = TRUE) plot_trajectory_confluence_matrix(flow, order_by = "cluster", bg = "grey20") ## End(Not run)
The braided-river panorama: every cluster-year node laid out by year (x), with
succession edges, node colour = terminal_group (or group), node size =
paper count, and births ringed. Uses the year-aware Sugiyama layout from
sniff_trajectory_dag()'s graph. Nodes from dormant lineages that never reach
the final year (a colour value outside the final groups) are drawn grey and
kept out of the colour legend.
plot_trajectory_dag( dag, color_by = c("terminal_group", "group"), edge_alpha = 0.25, point_size = 2.5, show_legend = FALSE, label_terminal = TRUE, labels_text = NULL, label_size = 3, title = "Cumulative network groups", legend_text_size = NULL, legend_ncol = NULL, axis_text_size = NULL )plot_trajectory_dag( dag, color_by = c("terminal_group", "group"), edge_alpha = 0.25, point_size = 2.5, show_legend = FALSE, label_terminal = TRUE, labels_text = NULL, label_size = 3, title = "Cumulative network groups", legend_text_size = NULL, legend_ncol = NULL, axis_text_size = NULL )
dag |
A |
color_by |
How to colour the nodes (default |
edge_alpha |
Base edge transparency, scaled by Jaccard weight (default 0.25). |
point_size |
Base node size (default 2.5). |
show_legend |
Show the colour legend (default |
label_terminal |
Label the final-year nodes with their bare group id
( |
labels_text |
Optional |
label_size |
Font size of the final-node labels (default |
title |
Plot title (default |
legend_text_size |
Font size of the colour-legend text; |
legend_ncol |
Number of columns in the colour legend; |
axis_text_size |
Font size of the x-axis (year) tick labels; |
The visual encoding:
The x-axis is the publication year (a Sugiyama layered layout); the y-axis has no intrinsic meaning, it only spreads nodes to reduce edge crossings.
Node colour is the trajectory basin (see color_by), node size is
the paper count, and succession edges fade with their Jaccard weight.
Births (nodes with no strong predecessor) are ringed in black.
Dormant lineages that never reach the final year are drawn grey and kept out of the colour legend, so the colours stand for the final groups only.
With label_terminal = TRUE the final-year nodes are labelled c1gN to the
right (with a leader line); with labels_text the colour legend moves to the
bottom as "c1gN: description" entries.
A ggplot object.
sniff_trajectory_dag(), plot_groups_per_year(),
plot_trajectory_dag_interactive()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: # docs_per_group comes from sniff_groups_lineage() dag <- sniff_trajectory_dag(docs_per_group) # the whole braid, coloured by destination group plot_trajectory_dag(dag) # human-readable bottom legend, two columns, larger year labels descr <- data.frame(id = c("c1g1", "c1g2"), text = c("Topic A", "Topic B")) plot_trajectory_dag(dag, labels_text = descr, legend_ncol = 2, axis_text_size = 14) ## End(Not run)## Not run: # docs_per_group comes from sniff_groups_lineage() dag <- sniff_trajectory_dag(docs_per_group) # the whole braid, coloured by destination group plot_trajectory_dag(dag) # human-readable bottom legend, two columns, larger year labels descr <- data.frame(id = c("c1g1", "c1g2"), text = c("Topic A", "Topic B")) plot_trajectory_dag(dag, labels_text = descr, legend_ncol = 2, axis_text_size = 14) ## End(Not run)
The plotly twin of plot_trajectory_dag(). Nodes are laid out by the same
year-aware Sugiyama layout, sized by paper count, and coloured by the final
group their lineage reaches. Dormant lineages that never reach the final year
are drawn grey. There is no legend: hovering a final-year node shows its group
id and labels_text description (c1gN: description), and hovering any other
(intermediate or dormant) node shows its raw id (e.g. y2021c1g2). When
label_terminal is TRUE, the
final group id (c1gN) is also printed to the right of each final-year node.
Returns a plotly htmlwidget.
plot_trajectory_dag_interactive( dag, labels_text = NULL, marker_range = c(4, 14), edge_alpha = 0.3, label_terminal = TRUE, label_size = 12, title = "Cumulative network groups" )plot_trajectory_dag_interactive( dag, labels_text = NULL, marker_range = c(4, 14), edge_alpha = 0.3, label_terminal = TRUE, label_size = 12, title = "Cumulative network groups" )
dag |
A |
labels_text |
Optional |
marker_range |
Min/max marker size in pixels; paper count is scaled into
this range (default |
edge_alpha |
Edge opacity, |
label_terminal |
Show the final group id ( |
label_size |
Font size (px) of the final-node labels (default |
title |
Plot title (default |
The interactive twin of plot_trajectory_dag(), built with plotly:
The x-axis is the publication year (Sugiyama layout); the y-axis has no intrinsic meaning.
Node colour is the trajectory basin (the final group reached); dormant
lineages that never reach the final year are grey. Node size scales with
paper count over marker_range.
There is no legend. Hovering a final-year node shows c1gN: description
(its group id plus the labels_text description); hovering any other node
shows its raw id (yYYYYcXgN).
Edges are a single faint layer (edge_alpha) and are excluded from hover.
With label_terminal = TRUE the final group ids (c1gN) are printed in a
de-overlapped column to the right, each joined to its node by a leader line.
The widget renders in the RStudio Viewer, embeds in Quarto/Shiny, or saves to a
standalone file with htmlwidgets::saveWidget().
A plotly htmlwidget.
plot_trajectory_dag(), plot_groups_per_year()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: # docs_per_group comes from sniff_groups_lineage() dag <- sniff_trajectory_dag(docs_per_group) plot_trajectory_dag_interactive(dag) # hover the final nodes for descriptions; fainter edges descr <- data.frame(id = c("c1g1", "c1g2"), text = c("Topic A", "Topic B")) p <- plot_trajectory_dag_interactive(dag, labels_text = descr, edge_alpha = 0.2) # htmlwidgets::saveWidget(p, "trajectory_dag.html", selfcontained = TRUE) ## End(Not run)## Not run: # docs_per_group comes from sniff_groups_lineage() dag <- sniff_trajectory_dag(docs_per_group) plot_trajectory_dag_interactive(dag) # hover the final nodes for descriptions; fainter edges descr <- data.frame(id = c("c1g1", "c1g2"), text = c("Topic A", "Topic B")) p <- plot_trajectory_dag_interactive(dag, labels_text = descr, edge_alpha = 0.2) # htmlwidgets::saveWidget(p, "trajectory_dag.html", selfcontained = TRUE) ## End(Not run)
Draws the selected intermediate (stagnant) trajectory as a short spine on the
left and fans out, from its handoff year, one curved link to every final
trajectory its terminal cohort reached (the full destination split), each
link's width scaled by the papers that landed there and labelled
tr::cNgN (n). It answers, visually, "where did this trajectory go?" – not
just the dominant absorber but the complete dispersion. Styling follows the
other flow plots (plot_trajectory_lines_2d(), plot_trajectory_confluence()):
a Set2/hue palette keyed by final group, a year-aware x-axis, and the shared
year_range / axis_text_size / palette controls.
plot_trajectory_dispersion( destination, title = NULL, label_size = 4.5, year_range = NULL, axis_text_size = NULL, palette = NULL, min_n = 0 )plot_trajectory_dispersion( destination, title = NULL, label_size = 4.5, year_range = NULL, axis_text_size = NULL, palette = NULL, min_n = 0 )
destination |
Output of |
title |
Plot title. Defaults to a |
label_size |
Text size for the in-plot labels (default: 4.5). |
year_range |
Optional |
axis_text_size |
Font size of the x-axis (year) tick labels; |
palette |
Optional named colour vector overriding the default hues (keyed
by final group |
min_n |
Minimum papers a final must receive to be drawn (default |
A ggplot object.
sniff_trajectory_destination(), plot_trajectory_confluence(),
plot_trajectory_lines_2d()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: flow <- sniff_trajectory_braid(docs_per_group) d <- sniff_trajectory_destination(flow, "tr1") plot_trajectory_dispersion(d) ## End(Not run)## Not run: flow <- sniff_trajectory_braid(docs_per_group) d <- sniff_trajectory_destination(flow, "tr1") plot_trajectory_dispersion(d) ## End(Not run)
A quadrant scatter of sniff_trajectory_dynamics() output, one figure per
population, designed to make the dynamic states legible at a glance. Each point
is a trajectory, its area the community size, its colour the state; the regions
are shaded and named after the state they classify, with the thresholds drawn as
reference lines.
plot_trajectory_dynamics( dyn, target = c("finals", "intermediary"), thresholds = NULL, labels_text = NULL, label_id = FALSE, size_range = c(2, 14), label_size = 3.5, axis_text_size = NULL, palette = NULL, show_thresholds = TRUE, show_quadrants = TRUE, xlim = NULL, ylim = NULL, legend_position = "right", title = NULL )plot_trajectory_dynamics( dyn, target = c("finals", "intermediary"), thresholds = NULL, labels_text = NULL, label_id = FALSE, size_range = c(2, 14), label_size = 3.5, axis_text_size = NULL, palette = NULL, show_thresholds = TRUE, show_quadrants = TRUE, xlim = NULL, ylim = NULL, legend_position = "right", title = NULL )
dyn |
A |
target |
What to draw, following |
thresholds |
The |
labels_text |
Optional |
label_id |
Prefix each living-core label with its group id, as
|
size_range |
Point-area range |
label_size |
Text size of the point labels (default |
axis_text_size |
Font size of the axis tick labels; |
palette |
Optional named colour vector overriding the default semantic
hues, keyed by state ( |
show_thresholds |
Draw the classification cut lines (default |
show_quadrants |
Shade and name the state regions (default |
xlim, ylim
|
Optional |
legend_position |
Legend placement (default |
title |
Plot title; |
"finals" – x = novelty, y = growth_rate; emergence is the
novel-and-growing region, dormancy the loss-of-momentum band, maturity the
saturated middle.
"intermediary" – x = dest_entropy, y = dormant_share; convergence is
the low-entropy (single destination) side, divergence the high-entropy side,
and dormancy the high-dropout band (rare, so this band is usually sparse).
Extinct (group NA) lineages have no coordinates and are reported in the
caption rather than drawn.
A ggplot object.
sniff_trajectory_dynamics(), fixed_state_thresholds(),
plot_trajectory_confluence()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: flow <- sniff_trajectory_braid(docs_per_group) dyn <- sniff_trajectory_dynamics(flow) descr <- data.frame(id = "c1g1", text = "Anaerobic digestion") plot_trajectory_dynamics(dyn, target = "finals", labels_text = descr) plot_trajectory_dynamics(dyn, target = "intermediary") ## End(Not run)## Not run: flow <- sniff_trajectory_braid(docs_per_group) dyn <- sniff_trajectory_dynamics(flow) descr <- data.frame(id = "c1g1", text = "Anaerobic digestion") plot_trajectory_dynamics(dyn, target = "finals", labels_text = descr) plot_trajectory_dynamics(dyn, target = "intermediary") ## End(Not run)
The plotly twin of plot_trajectory_dynamics(). The same quadrant map – shaded
and named state regions, classification thresholds as reference lines, points
sized by community and coloured by state – rendered as an HTML widget. Hovering
a point reveals the full indicator panel (growth, novelty, recruitment, inflow,
destination entropy, dormant share, and any CCT / hub-role columns), and the
state legend is clickable to isolate one state at a time.
plot_trajectory_dynamics_interactive( dyn, target = c("finals", "intermediary"), thresholds = NULL, labels_text = NULL, label_id = FALSE, marker_range = c(6, 42), show_labels = FALSE, label_size = 11, palette = NULL, show_thresholds = TRUE, show_quadrants = TRUE, xlim = NULL, ylim = NULL, title = NULL )plot_trajectory_dynamics_interactive( dyn, target = c("finals", "intermediary"), thresholds = NULL, labels_text = NULL, label_id = FALSE, marker_range = c(6, 42), show_labels = FALSE, label_size = 11, palette = NULL, show_thresholds = TRUE, show_quadrants = TRUE, xlim = NULL, ylim = NULL, title = NULL )
dyn |
A |
target |
|
thresholds |
The |
labels_text |
Optional |
label_id |
Put the id on the label's own first line, above the
description ( |
marker_range |
Marker diameter range in px |
show_labels |
Pin a text label to each point (default |
label_size |
Font size (px) of the pinned labels (default |
palette |
Optional named colour vector overriding the default semantic hues, keyed by state. |
show_thresholds |
Draw the classification cut lines (default |
show_quadrants |
Shade and name the state regions (default |
xlim, ylim
|
Optional |
title |
Plot title; |
A plotly htmlwidget.
plot_trajectory_dynamics(), sniff_trajectory_dynamics(),
fixed_state_thresholds()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_formation(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: flow <- sniff_trajectory_braid(docs_per_group) dyn <- sniff_trajectory_dynamics(flow) plot_trajectory_dynamics_interactive(dyn, target = "finals", labels_text = descr) ## End(Not run)## Not run: flow <- sniff_trajectory_braid(docs_per_group) dyn <- sniff_trajectory_dynamics(flow) plot_trajectory_dynamics_interactive(dyn, target = "finals", labels_text = descr) ## End(Not run)
Draws the target trajectory as a central spine and each feeder as a parallel
timeline that grows along its length (see feeder_curve) and merges into the
spine at its handoff year. The spine is a cumulative river: it widens smoothly
as each drawn feeder joins, so by the final year its width reflects the total
papers absorbed. Spine and feeder widths share one strictly proportional scale
(width 0 at zero papers), so their thickness is directly comparable. It
generalises plot_trajectory_dispersion() (one
source into one absorber) to many feeders converging on one trajectory,
answering visually "which trajectories formed this one?".
plot_trajectory_formation( formation, feeder_curve = c("inflow", "size"), max_feeders = 8, title = NULL, label_size = 4.5, year_range = NULL, axis_text_size = NULL, palette = NULL )plot_trajectory_formation( formation, feeder_curve = c("inflow", "size"), max_feeders = 8, title = NULL, label_size = 4.5, year_range = NULL, axis_text_size = NULL, palette = NULL )
formation |
Output of |
feeder_curve |
What the feeder's growing width traces: |
max_feeders |
Maximum number of feeders to draw, taken by descending papers (default: 8). Feeders beyond this are summarised in the caption. |
title |
Plot title. Defaults to the target trajectory key. |
label_size |
Text size for the feeder and target labels (default: 4.5). |
year_range |
Optional |
axis_text_size |
Font size of the x-axis (year) tick labels; |
palette |
Optional named colour vector overriding the default hues (keyed
by feeder |
A ggplot object.
sniff_trajectory_formation(), plot_trajectory_lines_2d(),
plot_trajectory_dispersion()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_lines_2d(),
plot_trajectory_lines_3d()
## Not run: flow <- sniff_trajectory_braid(groups_lineage) f <- sniff_trajectory_formation(flow, target = "tr::c1g3") plot_trajectory_formation(f) ## End(Not run)## Not run: flow <- sniff_trajectory_braid(groups_lineage) f <- sniff_trajectory_formation(flow, target = "tr::c1g3") plot_trajectory_formation(f) ## End(Not run)
The line counterpart of plot_trajectory_confluence() over the global flow
DAG. Every global trajectory is drawn as a variable-width line through the
year-aware Sugiyama layout: the central finals tr::cNgN and the intermediate
trN that feed them, each line widening with the documents accumulated along
its path. A bottom legend reports, for every intermediate, how many papers of
its terminal cohort reached each final group – a single trN can feed more
than one final (its terminal cohort splits across groups), which the legend
makes explicit.
plot_trajectory_lines_2d( flow, target = "all", conf = NULL, min_n = 5, min_prop = 0.05, min_total_size = 0, min_duration_years = 0, min_target_n = 0, width_range = c(0.8, 6), use_raw_papers = FALSE, label_size = 4, dest_min_prop = 0.05, lowlight_alpha = 0.18, lowlight_color = "#9AA5B1", legend_ncol = NULL, legend_text_size = NULL, axis_text_size = NULL, year_range = NULL, palette = NULL, title = NULL )plot_trajectory_lines_2d( flow, target = "all", conf = NULL, min_n = 5, min_prop = 0.05, min_total_size = 0, min_duration_years = 0, min_target_n = 0, width_range = c(0.8, 6), use_raw_papers = FALSE, label_size = 4, dest_min_prop = 0.05, lowlight_alpha = 0.18, lowlight_color = "#9AA5B1", legend_ncol = NULL, legend_text_size = NULL, axis_text_size = NULL, year_range = NULL, palette = NULL, title = NULL )
flow |
A |
target |
What to draw. |
conf |
An optional precomputed |
min_n, min_prop, min_total_size, min_duration_years
|
Feeder thresholds,
passed to the same pruning used by |
min_target_n |
Minimum papers an intermediate must leave in the selected
final(s) by the last year to be drawn (default |
width_range |
Line-width range |
use_raw_papers |
Scale widths by raw |
label_size |
Text size of the |
dest_min_prop |
In the |
lowlight_alpha, lowlight_color
|
Opacity and colour of the background
(non-trajectory) edges (defaults |
legend_ncol |
Number of columns in the feeding legend; |
legend_text_size |
Font size (pt) of the legend text; |
axis_text_size |
Font size (pt) of the x-axis (year) tick labels; |
year_range |
Optional |
palette |
Optional named colour vector overriding the default hues (keyed
by trajectory |
title |
Plot title; |
x = publication year (Sugiyama layers); the y-axis has no intrinsic meaning (a branching position).
Line width grows with cumulative tracked documents along the trajectory.
A single-year feeder has no edge to draw as a line and is dropped (it
would otherwise show a label/legend with no visible line); a single-year
central (the selected final, e.g. a final-year-only community like
tr::c1g17) is kept and drawn as a point.
Colour is per trajectory. With a single targeted final the legend is
summarised to trN: p% (n) – n of trN's papers landed in that final,
which is p% of trN's size (the feeders' counts overlap, so they do not
sum to the final's total). With target = "all" it shows the full split
trN -> c1g1:n, c1g2:n. A central is tr::cNgN (final, n), where n is the
documents that remained in its final-year community (the last point of
its size curve), not the cumulative documents the trajectory ever held.
A ggplot object.
plot_trajectory_lines_3d(), plot_trajectory_confluence(),
sniff_trajectory_braid(), sniff_trajectory_confluence()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_3d()
## Not run: flow <- sniff_trajectory_braid(docs_per_group) plot_trajectory_lines_2d(flow, min_n = 20) plot_trajectory_lines_2d(flow, target = "tr::c1g1") ## End(Not run)## Not run: flow <- sniff_trajectory_braid(docs_per_group) plot_trajectory_lines_2d(flow, min_n = 20) plot_trajectory_lines_2d(flow, target = "tr::c1g1") ## End(Not run)
The 3D counterpart of plot_trajectory_lines_2d(). Every global trajectory
is drawn over the
year-aware Sugiyama layout as a growing-thickness line: x = publication
year, y = route (the Sugiyama branching coordinate that separates
trajectories), and z = cumulative tracked documents along the path. The
central finals tr::cNgN and the intermediate trN that feed them share one
colour-per-trajectory scheme; the legend reports, for every intermediate, how
many papers of its terminal cohort reached each final (a single trN can feed
more than one final, which the legend makes explicit). Selection and thresholds
are identical to plot_trajectory_lines_2d(); only the rendering adds the
document z-axis and interactive hover.
plot_trajectory_lines_3d( flow, target = "all", conf = NULL, min_n = 5, min_prop = 0.05, min_total_size = 0, min_duration_years = 0, min_target_n = 0, width_range = c(4, 12), use_raw_papers = FALSE, log_scale = FALSE, label_size = 16, hover_font_size = 12, dest_min_prop = 0.05, lowlight_alpha = 0.75, lowlight_color = "#9AA5B1", lowlight_width = 3, show_background = TRUE, year_range = NULL, palette = NULL, title = NULL, legend_position = c("bottom", "right", "top", "left", "none"), legend_ncol = NULL, labels_text = NULL )plot_trajectory_lines_3d( flow, target = "all", conf = NULL, min_n = 5, min_prop = 0.05, min_total_size = 0, min_duration_years = 0, min_target_n = 0, width_range = c(4, 12), use_raw_papers = FALSE, log_scale = FALSE, label_size = 16, hover_font_size = 12, dest_min_prop = 0.05, lowlight_alpha = 0.75, lowlight_color = "#9AA5B1", lowlight_width = 3, show_background = TRUE, year_range = NULL, palette = NULL, title = NULL, legend_position = c("bottom", "right", "top", "left", "none"), legend_ncol = NULL, labels_text = NULL )
flow |
A |
target |
What to draw. |
conf |
An optional precomputed |
min_n, min_prop, min_total_size, min_duration_years
|
Feeder thresholds,
passed to the same pruning used by |
min_target_n |
Minimum papers an intermediate must leave in the selected
final(s) by the last year to be drawn (default |
width_range |
Line-width range |
use_raw_papers |
Scale z and width by raw |
log_scale |
Apply |
label_size |
Font size of the |
hover_font_size |
Font size for hover tooltips (default |
dest_min_prop |
In the |
lowlight_alpha, lowlight_color, lowlight_width
|
Opacity, colour and width of the light-grey links between trajectories. |
show_background |
Draw the light-grey links between trajectories – the
edges of the DAG that are not on any single trajectory's path (the hand-offs
and forks), at the nodes' real heights, as in |
year_range |
Optional |
palette |
Optional named colour vector overriding the default hues (keyed
by trajectory |
title |
Plot title; |
legend_position |
Where to place the legend: |
legend_ncol |
Number of columns in the legend (default |
labels_text |
Optional |
z = cumulative tracked documents along each trajectory (raw or weighted),
accumulated node by node; log_scale = TRUE compresses it.
Line width grows per trajectory with the same cumulative measure, so each
line is thinnest at its birth and thickest at its terminal node (the width is
rescaled within each trajectory, matching plot_trajectory_lines_2d()).
A single-year central (e.g. a final-year-only community like tr::c1g17)
has no segment to draw and is rendered as a terminal ball; a single-year
feeder is dropped (it would show a label with no visible line).
Colour is per trajectory and the legend carries the feeding split: a
central reads tr::cNgN (final, n) (n = documents that remained in its
final-year community), an intermediate reads trN -> c1g1:n, c1g2:n (its
terminal cohort split across finals). In a single-target view it reads
trN: p% (n) -> c1g15:n, c1g1:n, ... – p% and the parenthesised (n) are
the share and the document count that reached the target (as in
plot_trajectory_lines_2d()); the split then shows the feeder's full
destination, so a feeder of c1g1 reveals that more of it may flow elsewhere.
Hovering a node shows that same feeding-legend text, the trajectory's
labels_text description (when supplied), the node's year, and the absolute
documents in that cluster-year (quantity_papers, not the running cumulative
that sets the height).
A plotly 3D plot object.
plot_trajectory_lines_2d(), sniff_trajectory_braid(),
sniff_trajectory_confluence()
Other visualization:
plot_groups_influence_matrix(),
plot_groups_influence_network(),
plot_groups_lineage_2d(),
plot_groups_lineage_3d(),
plot_groups_map(),
plot_groups_map_animation(),
plot_groups_map_interactive(),
plot_groups_per_year(),
plot_trajectory_confluence(),
plot_trajectory_confluence_interactive(),
plot_trajectory_confluence_matrix(),
plot_trajectory_dag(),
plot_trajectory_dag_interactive(),
plot_trajectory_dispersion(),
plot_trajectory_dynamics(),
plot_trajectory_dynamics_interactive(),
plot_trajectory_formation(),
plot_trajectory_lines_2d()
## Not run: flow <- sniff_trajectory_braid(docs_per_group) plot_trajectory_lines_3d(flow, min_n = 20) plot_trajectory_lines_3d(flow, target = "tr::c1g1") ## End(Not run)## Not run: flow <- sniff_trajectory_braid(docs_per_group) plot_trajectory_lines_3d(flow, min_n = 20) plot_trajectory_lines_3d(flow, target = "tr::c1g1") ## End(Not run)
Parse datasets exported from OpenAlex in two ways:
(1) a CSV file exported in the browser, or
(2) a data frame obtained via the {openalexR} API helpers.
The function standardizes fields to common bibliographic tags (e.g., AU,
SO, CR, PY, DI) and returns a tidy tibble.
read_openalex(file, format = "csv")read_openalex(file, format = "csv")
file |
For |
format |
Either |
CSV mode (format = "csv"):
If file is a URL, it is downloaded to a temporary file before parsing
(a progress message is printed).
Selected fields are mapped to standardized tags:
id_short (short OpenAlex ID), SR (= id_short), PY (= publication_year),
TI (= title), DI (= doi), DT (= type), DE (= keywords.display_name),
AB (= abstract), AU (= authorships.author.display_name),
SO (= locations.source.display_name),
C1 (= authorships.countries), TC (= cited_by_count),
SC (= primary_topic.field.display_name), CR (= referenced_works,
with the https://openalex.org/ prefix stripped),
and DB = "openalex_csv".
PY is coerced to numeric; a helper column DI2 (uppercase, punctuation-stripped
variant of DI) is added; columns with all-caps tags are placed first and
DI2 is relocated after DI.
API mode (format = "api"):
file must be a data frame containing at least column id; typically this
is returned by openalexR::oa_request() + openalexR::oa2df() or similar.
Records are filtered to type %in% c("article","review") and deduplicated by id.
The function derives:
id_short (= id without the https://openalex.org/ prefix) and SR (= id_short);
CR: concatenated short IDs from referenced_works (semicolon-separated);
DE: concatenated keyword names (lower case) from keywords;
AU: concatenated author names (upper case) from authorships;
plus core fields PY (= publication_year), TC (= cited_by_count),
TI (= title), AB (= abstract), DI (= doi),
and DB = "openalex_api".
The result keeps one row per id and may include original columns from the
input (via a right join), after constructing the standardized fields above.
A tibble with standardized bibliographic columns. Typical output includes:
id_short, AU, DI, CR, SO, DT, DE, AB, C1, TC, SC, SR,
PY, and DB (source flag: "openalex_csv" or "openalex_api"). See Details.
format = "csv" — a local path or an HTTP(S) URL to an OpenAlex CSV export.
format = "api" — a data frame produced by {openalexR} for the
works entity (with the usual OpenAlex columns, including list-columns
such as keywords, authorships, and referenced_works).
OpenAlex R client: oa_request, oa2df.
Importers for Web of Science: read_wos.
## Not run: ## CSV export (local path) x <- read_openalex("openalex-works.csv", format = "csv") ## Using the API with openalexR library(openalexR) url_api <- "https://api.openalex.org/works?page=1&filter=primary_location.source.id:s121026525" df_api <- openalexR::oa_request(query_url = url_api) |> openalexR::oa2df(entity = "works") y <- read_openalex(df_api, format = "api") ## End(Not run)## Not run: ## CSV export (local path) x <- read_openalex("openalex-works.csv", format = "csv") ## Using the API with openalexR library(openalexR) url_api <- "https://api.openalex.org/works?page=1&filter=primary_location.source.id:s121026525" df_api <- openalexR::oa_request(query_url = url_api) |> openalexR::oa2df(entity = "works") y <- read_openalex(df_api, format = "api") ## End(Not run)
Parse Web of Science (WoS) export files in multiple formats and return a
tidy table. The function automatically dispatches to a specialized parser
based on the format argument and can also download from a URL if
file points to an http:// or https:// resource.
read_wos(file, format = "bib", normalized_names = TRUE)read_wos(file, format = "bib", normalized_names = TRUE)
file |
Character scalar or vector. Path(s) to a WoS export file, or a
single URL ( |
format |
Character scalar. Export format; one of
|
normalized_names |
Logical. If |
file may be a single path/URL or a vector of paths; multiple files
will be combined row-wise when applicable.
When file is a URL, the file is downloaded to a temporary path before
parsing (a progress message is printed).
If normalized_names = TRUE, selected WoS tags are mapped to standardized
names (e.g., AU -> author, TI -> title, PY -> year, DI -> doi,
DE -> keywords, SR -> unique_id, etc.; the exact mapping depends on
the format). Otherwise, original field tags are preserved.
The output includes:
DI2: an uppercase, punctuation-stripped variant of DI (if present),
PY: coerced to numeric (when present),
DB: a provenance flag indicating the source/format and whether names
were normalized.
Columns with ALL-CAPS tags (e.g., AU, TI, PY) are placed first,
followed by other columns, and DI2 is relocated just after DI.
A tibble with the parsed WoS records. See Details for notes on
added/coerced columns (DI2, PY, DB) and column ordering.
"bib" — BibTeX export
"ris" — RIS export
"txt-plain-text" — Plain-text export
"txt-tab-delimited" — Tab-delimited export
Internal parsers used by this function:
read_wos_bib, read_wos_ris,
read_wos_plain, read_wos_tab.
bib_file <- system.file("extdata", "sample_wos.bib", package = "birddog") M <- read_wos(bib_file, format = "bib", normalized_names = TRUE) head(M) ## Not run: # load data from a URL M <- read_wos("https://example.com/savedrecs.bib", format = "bib") ## End(Not run)bib_file <- system.file("extdata", "sample_wos.bib", package = "birddog") M <- read_wos(bib_file, format = "bib", normalized_names = TRUE) head(M) ## Not run: # load data from a URL M <- read_wos("https://example.com/savedrecs.bib", format = "bib") ## End(Not run)
Calculates the Citation Cycle Time (CCT) to measure the pace of scientific or technological progress in a publication network. Based on Kayal (1999), the indicator measures the median age of cited publications, where lower values indicate faster knowledge replacement cycles.
sniff_cct( network, scope = "groups", start_year = NULL, end_year = NULL, tracked_cr_py = NULL, batch_size = 50, min_papers_per_year = 3, rolling_window = NULL, normalize = "none", dispersion = FALSE, citation_scope = "global" )sniff_cct( network, scope = "groups", start_year = NULL, end_year = NULL, tracked_cr_py = NULL, batch_size = 50, min_papers_per_year = 3, rolling_window = NULL, normalize = "none", dispersion = FALSE, citation_scope = "global" )
network |
Required. Network object containing publication data. For |
scope |
Analysis scope. Either |
start_year, end_year
|
Start and end years for temporal analysis. If not specified, uses minimum and maximum years found in the data. |
tracked_cr_py |
Pre-processed citation year data (optional). A tibble with columns
|
batch_size |
For OpenAlex data: number of IDs to process per API call (default: 50). Smaller batches help avoid API rate limits, larger batches process data faster but may trigger rate limiting. |
min_papers_per_year |
Minimum number of papers required in a given year to compute CCT. Years with fewer papers are reported as NA (default: 3). |
rolling_window |
Optional integer for rolling window smoothing. If provided, CCT values are smoothed using a centered moving average of the specified width (e.g., 3 for a 3-year window). Default is NULL (no smoothing). |
normalize |
Normalization mode. Either |
dispersion |
Logical. If |
citation_scope |
Either |
The Citation Cycle Time (CCT) is calculated following Kayal (1999):
Extract citation IDs from the network's CR column
Fetch publication years for cited works from OpenAlex API using get_openalex_fields()
For each publication, calculate the age of each cited reference (PY - CR_PY)
Calculate the median citation age per publication
For each year, calculate the median of per-publication medians across all publications in that year (annual mode)
Lower CCT values indicate that publications are citing more recent work, suggesting a faster pace of knowledge replacement. A sudden drop in CCT within a group signals potential scientific emergence.
The function automatically handles:
Splitting semicolon-separated citation IDs
Batch processing of OpenAlex API requests
Filtering invalid citations (where cited work was published after citing work)
Skipping years with too few papers (min_papers_per_year)
Optional rolling window smoothing for noisy time series
Creating temporal plots for each group
A list with the following components:
data |
Tibble with CCT data containing columns: group, year, index.
When |
plots |
Named list of plotly objects showing temporal evolution of CCT for each group. Each plot shows both absolute CCT values and year-over-year differences. |
years_range |
Named vector with start_year and end_year used in the analysis |
tracked_cr_py |
Citation year data with columns CR and CR_PY. Can be saved and reused in subsequent analyses to avoid repeated API calls. |
Kayal AA, Waters RC. An empirical evaluation of the technology cycle time indicator as a measure of the pace of technological progress in superconductor technology. IEEE Transactions on Engineering Management. 1999;46(2):127-31. doi:10.1109/17.759138
sniff_groups(), get_openalex_fields(), indexes_plots()
## Not run: # Group analysis results <- sniff_cct(network_groups, scope = "groups") # Network analysis results_network <- sniff_cct(complete_network, scope = "network") # With rolling window smoothing results_smooth <- sniff_cct( network_groups, scope = "groups", rolling_window = 3 ) # Domain-normalized CCT (values around 1; <1 = short-cycle relative to domain) results_norm <- sniff_cct( network_groups, scope = "groups", normalize = "domain" ) # With dispersion (adds p25 and p75 columns to detect bimodal distributions) results_disp <- sniff_cct( network_groups, scope = "groups", dispersion = TRUE ) # Local citations only (intra-corpus references) results_local <- sniff_cct( network_groups, scope = "groups", citation_scope = "local" ) # Accessing results cct_data <- results$data plots <- results$plots plots$c1g1 # View plot for specific group # Reuse citation data to avoid repeated API calls saved_citations <- results$tracked_cr_py results2 <- sniff_cct( network_groups, tracked_cr_py = saved_citations ) ## End(Not run)## Not run: # Group analysis results <- sniff_cct(network_groups, scope = "groups") # Network analysis results_network <- sniff_cct(complete_network, scope = "network") # With rolling window smoothing results_smooth <- sniff_cct( network_groups, scope = "groups", rolling_window = 3 ) # Domain-normalized CCT (values around 1; <1 = short-cycle relative to domain) results_norm <- sniff_cct( network_groups, scope = "groups", normalize = "domain" ) # With dispersion (adds p25 and p75 columns to detect bimodal distributions) results_disp <- sniff_cct( network_groups, scope = "groups", dispersion = TRUE ) # Local citations only (intra-corpus references) results_local <- sniff_cct( network_groups, scope = "groups", citation_scope = "local" ) # Accessing results cct_data <- results$data plots <- results$plots plots$c1g1 # View plot for specific group # Reuse citation data to avoid repeated API calls saved_citations <- results$tracked_cr_py results2 <- sniff_cct( network_groups, tracked_cr_py = saved_citations ) ## End(Not run)
Detects connected components in a citation network and computes summary statistics for each component. Returns both the component information and an updated network with component labels.
sniff_components(net)sniff_components(net)
net |
A network object (tbl_graph or igraph) generated by |
A list with two elements:
A tibble with component statistics containing:
component: Component identifier (e.g., "c1")
quantity_publications: Number of publications in component
average_age: Mean publication year of component
The input network with added component labels
Other groups (stock):
sniff_groups(),
sniff_groups_attributes(),
sniff_groups_cumulative(),
sniff_groups_cumulative_citations(),
sniff_groups_hubs(),
sniff_groups_influence(),
sniff_groups_lineage(),
sniff_network()
## Not run: # Create a network first data <- read_wos("savedrecs.txt") net <- sniff_network(data) # Analyze components result <- sniff_components(net) # Access component information result$components # Get network with component labels component_net <- result$network ## End(Not run)## Not run: # Create a network first data <- read_wos("savedrecs.txt") net <- sniff_network(data) # Analyze components result <- sniff_components(net) # Access component information result$components # Get network with component labels component_net <- result$network ## End(Not run)
Computes the normalized Shannon entropy of keyword distributions from scientific publications over a specified time range. Entropy measures the diversity and evenness of keyword usage within research groups or the entire network.
sniff_entropy( network, scope = "groups", start_year = NULL, end_year = NULL, mode = "rolling", window_size = 5 )sniff_entropy( network, scope = "groups", start_year = NULL, end_year = NULL, mode = "rolling", window_size = 5 )
network |
A network object to analyze. For |
scope |
Character specifying the analysis scope: "groups" for multiple groups or "network" for the entire network (default: "groups"). |
start_year |
Starting year for entropy calculation. If NULL, uses the minimum publication year found in the network data. |
end_year |
Ending year for entropy calculation. If NULL, uses the maximum publication year found in the network data. |
mode |
Character specifying the temporal mode for entropy calculation:
|
window_size |
Integer specifying the rolling window size in years (default: 5).
Only used when |
The function calculates the normalized Shannon entropy (Pielou's evenness index)
based on Shannon's information theory (Shannon, 1948). The temporal scope of
keyword data depends on the mode parameter:
annual: entropy from keywords published in each specific year. Values tend to be high (near 1) since within-year distributions are typically even.
cumulative: entropy from all keywords published up to each year. Shows long-term trends in thematic concentration as the keyword pool grows.
rolling: entropy from a sliding window of recent years. Balances sensitivity to recent shifts with enough data for stable estimates.
The normalized entropy (Pielou's J') is calculated as:
where is the relative frequency of keyword , is the
number of unique keywords, and is the maximum possible
entropy for categories.
Entropy values range from 0 to 1, where:
0 indicates minimal diversity (one dominant keyword)
1 indicates maximal diversity (all keywords equally frequent)
A sudden increase in entropy may signal the emergence of new research topics, while a decrease suggests thematic convergence.
A list with three components:
data |
A tibble containing entropy values for each group and year |
plots |
A list of plotly objects visualizing entropy trends for each group |
years_range |
A vector with the start_year and end_year used in calculations |
Shannon, C. E. (1948). A mathematical theory of communication. Bell System Technical Journal, 27(3), 379-423. doi:10.1002/j.1538-7305.1948.tb01338.x
Pielou, E. C. (1966). The measurement of diversity in different types of biological collections. Journal of Theoretical Biology, 13, 131-144.
sniff_groups, sniff_network, indexes_plots
## Not run: # Rolling window (default: 5 years) entropy_results <- sniff_entropy(groups_data, scope = "groups") # Cumulative mode entropy_results <- sniff_entropy(groups_data, mode = "cumulative") # Annual mode entropy_results <- sniff_entropy(groups_data, mode = "annual") # Rolling window with custom size entropy_results <- sniff_entropy(groups_data, mode = "rolling", window_size = 3) # Access results entropy_data <- entropy_results$data entropy_plots <- entropy_results$plots ## End(Not run)## Not run: # Rolling window (default: 5 years) entropy_results <- sniff_entropy(groups_data, scope = "groups") # Cumulative mode entropy_results <- sniff_entropy(groups_data, mode = "cumulative") # Annual mode entropy_results <- sniff_entropy(groups_data, mode = "annual") # Rolling window with custom size entropy_results <- sniff_entropy(groups_data, mode = "rolling", window_size = 3) # Access results entropy_data <- entropy_results$data entropy_plots <- entropy_results$plots ## End(Not run)
This function identifies and analyzes groups (communities) within scientific networks created from articles and patents data. It can apply different clustering algorithms to detect technological trajectories and emerging scientific fields.
sniff_groups( comps, min_group_size = 10, keep_component = c("c1"), cluster_component = c("c1"), algorithm = "fast_greedy", seed = 888L )sniff_groups( comps, min_group_size = 10, keep_component = c("c1"), cluster_component = c("c1"), algorithm = "fast_greedy", seed = 888L )
comps |
A list containing network components, typically generated by
|
min_group_size |
Minimum size for a group to be included in results (default = 10). Groups with fewer members will be filtered out. |
keep_component |
Character vector specifying which network components to process (default = "c1"). Can include multiple components. |
cluster_component |
Character vector specifying which components should be clustered (default = "c1"). Components not listed here will be treated as single groups. |
algorithm |
Community detection algorithm to use (default = "fast_greedy"). Options include: "louvain", "walktrap", "edge_betweenness", "fast_greedy", or "leiden". |
seed |
Random seed for reproducible results (default = 888L). Only applies to algorithms that use random initialization like Louvain. |
The function first validates the input network, then applies the specified clustering algorithm to detect communities within the network. It calculates statistics for each detected group and returns the results along with the augmented network. The function can handle multiple network components simultaneously, applying clustering only to specified components.
A list with three elements:
aggregate: A data frame with group statistics including group name,
number of papers, and average publication year
network: The input network with added group attributes
pubs_by_year: Publication counts by group and year
sniff_components() for creating the input network components
Other groups (stock):
sniff_components(),
sniff_groups_attributes(),
sniff_groups_cumulative(),
sniff_groups_cumulative_citations(),
sniff_groups_hubs(),
sniff_groups_influence(),
sniff_groups_lineage(),
sniff_network()
## Not run: # Assuming 'comps' is output from sniff_components() groups <- sniff_groups(comps, min_group_size = 15, algorithm = "leiden", seed = 888L ) # Access group statistics groups$aggregate groups$network groups$pubs_by_year ## End(Not run)## Not run: # Assuming 'comps' is output from sniff_components() groups <- sniff_groups(comps, min_group_size = 15, algorithm = "leiden", seed = 888L ) # Access group statistics groups$aggregate groups$network groups$pubs_by_year ## End(Not run)
This function analyzes publication growth rates and other attributes for research groups identified in scientific networks. It calculates growth rates using exponential models, creates horizon plots for visualization, and generates summary tables.
sniff_groups_attributes( groups, growth_rate_period = 2010:2022, horizon_plot = TRUE, show_results = TRUE, assign_result = NULL )sniff_groups_attributes( groups, growth_rate_period = 2010:2022, horizon_plot = TRUE, show_results = TRUE, assign_result = NULL )
groups |
A list containing network data with publications by year and group information.
Must include elements: |
growth_rate_period |
Numeric vector of years to use for growth rate calculation (default: 2010:2024). |
horizon_plot |
Logical indicating whether to include horizon plots in the output table (default: TRUE). |
show_results |
Logical indicating whether to print results to console (default: TRUE). |
assign_result |
Character string specifying a variable name to assign the results to in the global environment (default: NULL). |
The function performs the following steps:
Calculates growth rates using exponential models for each group
Processes publication age and doubling time metrics
Optionally creates horizon plots for each group's publication trend
Generates a comprehensive summary table
A list with two components:
attributes_table: A gt table showing group attributes including growth rates
regression: A list of model summaries for each group's growth rate calculation
Other groups (stock):
sniff_components(),
sniff_groups(),
sniff_groups_cumulative(),
sniff_groups_cumulative_citations(),
sniff_groups_hubs(),
sniff_groups_influence(),
sniff_groups_lineage(),
sniff_network()
## Not run: # Assuming groups is output from sniff_groups() groups_attributes <- sniff_groups_attributes(groups, growth_rate_period = 2010:2022, horizon_plot = TRUE ) # View the results table print(groups_attributes$attributes_table) # Access model summaries groups_attributes$regression ## End(Not run)## Not run: # Assuming groups is output from sniff_groups() groups_attributes <- sniff_groups_attributes(groups, growth_rate_period = 2010:2022, horizon_plot = TRUE ) # View the results table print(groups_attributes$attributes_table) # Access model summaries groups_attributes$regression ## End(Not run)
Performs cumulative community detection on a network over specified time spans, returning group statistics and keyword analysis for each time period.
sniff_groups_cumulative( comps, time_span = NULL, min_group_size = 10, keep_component = c("c1"), cluster_component = c("c1"), top_n_keywords = 10, algorithm = "fast_greedy", seed = 888L )sniff_groups_cumulative( comps, time_span = NULL, min_group_size = 10, keep_component = c("c1"), cluster_component = c("c1"), top_n_keywords = 10, algorithm = "fast_greedy", seed = 888L )
comps |
A list containing network components, typically generated by
|
time_span |
Numeric vector of years to analyze (default: 2000:2024). |
min_group_size |
Minimum size for a cluster to be retained (default = 10). |
keep_component |
Character vector specifying which network components to process (default = "c1"). Can include multiple components. |
cluster_component |
Character vector specifying which components should be clustered (default = "c1"). Components not listed here will be treated as single groups. |
top_n_keywords |
Number of top keywords to extract per group (default = 10). |
algorithm |
Community detection algorithm to use. One of:
|
seed |
Random seed for reproducible results (default = 888L). Only applies to algorithms that use random initialization like Louvain. |
A named list (by year) where each element contains:
A tibble with group statistics and top keywords
A tibble mapping documents to groups
The cumulative network up to that year
Other groups (stock):
sniff_components(),
sniff_groups(),
sniff_groups_attributes(),
sniff_groups_cumulative_citations(),
sniff_groups_hubs(),
sniff_groups_influence(),
sniff_groups_lineage(),
sniff_network()
## Not run: # Typical pipeline: data <- read_wos("savedrecs.txt") net <- sniff_network(data) comps <- sniff_components(net) # Cumulative analysis groups_cumulative <- sniff_groups_cumulative( comps, time_span = 2010:2020, keep_component = c("c1", "c2"), cluster_component = c("c1"), algorithm = "leiden", seed = 888L ) # Access results for 2015 groups_cumulative[["network_until_2015"]]$groups ## End(Not run)## Not run: # Typical pipeline: data <- read_wos("savedrecs.txt") net <- sniff_network(data) comps <- sniff_components(net) # Cumulative analysis groups_cumulative <- sniff_groups_cumulative( comps, time_span = 2010:2020, keep_component = c("c1", "c2"), cluster_component = c("c1"), algorithm = "leiden", seed = 888L ) # Access results for 2015 groups_cumulative[["network_until_2015"]]$groups ## End(Not run)
This function calculates cumulative citations for papers within research groups, tracking how citations accumulate over time for highly cited papers.
sniff_groups_cumulative_citations(groups, min_citations = 5)sniff_groups_cumulative_citations(groups, min_citations = 5)
groups |
A list containing network data with the following components:
|
min_citations |
Minimum number of citations for a paper to be included in analysis (default: 10). |
For each research group, the function:
Identifies papers with citations above the threshold
Tracks citations to these papers year by year
Calculates cumulative citation patterns
Computes various growth metrics for citation analysis
Works with both Web of Science (WOS) and OpenAlex data formats.
A named list (by research group) where each element contains a tibble with:
group: Research group identifier
SR: Paper identifier
TC: Total citations
PY: Publication year
Ki: Total network citations
citations_by_year: A tibble with annual citation counts
(PY: year, citations: count)
growth_power: Growth power score (0-100)
growth_consistency: Percentage of years with citations
peak_momentum: Highest 3-year rolling average citation count
early_impact: Citations in first 5 years
recent_momentum: Citations in last 3 years
acceleration_factor: Ratio of late to early citations
Other groups (stock):
sniff_components(),
sniff_groups(),
sniff_groups_attributes(),
sniff_groups_cumulative(),
sniff_groups_hubs(),
sniff_groups_influence(),
sniff_groups_lineage(),
sniff_network()
## Not run: # Assuming groups is output from sniff_groups() # Calculate cumulative citations groups_cumulative_citations <- sniff_groups_cumulative_citations(groups, min_citations = 5) # View results for first group head(groups_cumulative_citations[[1]]) ## End(Not run)## Not run: # Assuming groups is output from sniff_groups() # Calculate cumulative citations groups_cumulative_citations <- sniff_groups_cumulative_citations(groups, min_citations = 5) # View results for first group head(groups_cumulative_citations[[1]]) ## End(Not run)
This function analyzes citation networks to identify hub papers within research groups based on their citation patterns. It calculates several metrics (Zi, Pi) to classify papers into different hub categories.
sniff_groups_hubs(groups, min_citations = 1)sniff_groups_hubs(groups, min_citations = 1)
groups |
A list containing network data with the following components:
|
min_citations |
Minimum number of citations for a paper to be considered (default: 1) |
The function classifies papers into hub categories based on:
R5: Knowledge hubs (Zi >= 2.5 and Pi <= 0.3)
R6: Bridging hubs (Zi >= 2.5 and 0.3 < Pi <= 0.75)
R7: Boundary-spanning hubs (Zi >= 2.5 and Pi > 0.75)
A tibble containing:
group: Research group identifier
SR: Paper identifier
TC: Total citations
Ki: Total citations from all groups
ki: Citations from within the same group
Zi: Standardized within-group citation score
Pi: Citation diversity index
zone: Hub classification ("noHub", "R5", "R6", "R7")
Other groups (stock):
sniff_components(),
sniff_groups(),
sniff_groups_attributes(),
sniff_groups_cumulative(),
sniff_groups_cumulative_citations(),
sniff_groups_influence(),
sniff_groups_lineage(),
sniff_network()
## Not run: # Assuming 'groups' is output from sniff_groups() # Identify hub papers hubs <- sniff_groups_hubs(groups, min_citations = 5) # View results head(hubs) ## End(Not run)## Not run: # Assuming 'groups' is output from sniff_groups() # Identify hub papers hubs <- sniff_groups_hubs(groups, min_citations = 5) # View results head(hubs) ## End(Not run)
Experimental. Lifts the internal citations of a sniff_groups() network to
the group level and measures how much each group's output flows into every
other group's. Influence is directional: if group 's papers cite
group 's papers, knowledge flows . The function returns
the cross-citation matrix, four size-corrected indices per ordered pair, the
net flow between pairs, and a source / broker / sink role per group.
sniff_groups_influence(groups, self = TRUE, null_reps = 0, seed = NULL) is_influence(x) ## S3 method for class 'birddog_influence' print(x, ...)sniff_groups_influence(groups, self = TRUE, null_reps = 0, seed = NULL) is_influence(x) ## S3 method for class 'birddog_influence' print(x, ...)
groups |
A |
self |
Keep the diagonal (intra-group citations) in the matrix and in
the out/in strengths. Default |
null_reps |
Number of group-label permutations for a per-channel
p-value: shuffle the group labels over documents, recompute the
cross-citation matrix, and record how often the random flow meets or beats
the observed count. Default |
seed |
Optional integer seed for the permutation null, for
reproducibility. Default |
x |
A |
... |
Ignored. |
Writing for the number of citations from group to group
(rows cite columns), out-strength , in-strength
, and total , each ordered
pair carries four normalizations:
debt – the share of 's citations owed to
(how much leans on );
audience – the share of 's citations
coming from ;
salton – a size-free channel
strength, symmetric in the pair;
surprise – the flow against the
configuration-model expectation; over-represented,
under-represented.
The net influence removes reciprocal flow,
and the group balance (received minus made)
classifies each group as a source (, foundational), a sink
(, frontier consumer), or a broker (). The
balances of a closed system sum to zero. The diagonal is
intra-group cohesion; self = FALSE drops it before any normalization (the
balances and net are unchanged, since the diagonal cancels).
An object of class birddog_influence, a list with:
matrix: the cross-citation matrix (rows
cite columns), groups in mixed_sort() order.
flow: a tibble, one row per observed ordered pair, sorted by
descending surprise – influencer (cited group), recipient
(citing group), citations, debt, audience, salton, surprise
(and p_value when null_reps > 0).
groups: per-group received, made, balance, role
(source / broker / sink), sorted by descending balance.
net: the net-influence edge list – from (source), to
(recipient), net ().
params: the call settings.
sniff_groups(), sniff_groups_hubs(),
plot_groups_influence_matrix(), plot_groups_influence_network()
Other groups (stock):
sniff_components(),
sniff_groups(),
sniff_groups_attributes(),
sniff_groups_cumulative(),
sniff_groups_cumulative_citations(),
sniff_groups_hubs(),
sniff_groups_lineage(),
sniff_network()
## Not run: groups <- sniff_groups(net) infl <- sniff_groups_influence(groups) infl infl$groups # source / broker / sink per group infl$net # who, on balance, leads whom plot_groups_influence_matrix(infl) ## End(Not run)## Not run: groups <- sniff_groups(net) infl <- sniff_groups_influence(groups) infl infl$groups # source / broker / sink per group infl$net # who, on balance, leads whom plot_groups_influence_matrix(infl) ## End(Not run)
This function processes nodes grouped in a network (typically by community detection), and extracts the most frequent and the most distinctive keywords (using TF-IDF) from a descriptor field such as keywords or subject terms.
sniff_groups_keywords(net_groups, n_terms = 15, min_freq = 1, sep = ";")sniff_groups_keywords(net_groups, n_terms = 15, min_freq = 1, sep = ";")
net_groups |
A list containing a |
n_terms |
Integer. The number of top terms to return per group, both by frequency and by TF-IDF. Default is 15. |
min_freq |
Integer. Minimum frequency a term must have in a group to be considered. Default is 2. |
sep |
Character. Separator used in the |
A tibble with one row per group, containing two columns:
term_freq: the most frequent terms (with raw frequency).
term_tfidf: the most distinctive terms (with TF-IDF scores).
## Not run: # Assuming 'groups' is output from sniff_groups() groups_keywords <- sniff_groups_keywords(groups) ## End(Not run)## Not run: # Assuming 'groups' is output from sniff_groups() groups_keywords <- sniff_groups_keywords(groups) ## End(Not run)
This function analyzes the evolution of document groups over time to detect technological trajectories and scientific emergence patterns. It computes similarity measures between groups across time periods and tracks their attributes.
sniff_groups_lineage( groups_cumulative, min_group_size = 10, top_n_keywords = 3 )sniff_groups_lineage( groups_cumulative, min_group_size = 10, top_n_keywords = 3 )
groups_cumulative |
A list of cumulative group data over time, typically produced by other functions in the birddog package. Each element should contain network, documents, and groups data. |
min_group_size |
Minimum number of documents required for a group to be considered (default: 10). Smaller groups will be filtered out. |
top_n_keywords |
Number of top keywords to consider when analyzing group characteristics (default: 3). |
Prepares lineage data (docs_per_group, groups_similarity, groups_attributes);
detects no trajectories – see sniff_trajectory_braid().
A list with three components:
groups_attributes: A list of data frames containing attributes for each tracked group
groups_similarity: A list of data frames containing Jaccard similarity measures between groups across time periods
docs_per_group: A data frame containing document IDs for all groups across time periods
Other groups (stock):
sniff_components(),
sniff_groups(),
sniff_groups_attributes(),
sniff_groups_cumulative(),
sniff_groups_cumulative_citations(),
sniff_groups_hubs(),
sniff_groups_influence(),
sniff_network()
## Not run: # Assuming you have cumulative group data: trajectories <- sniff_groups_lineage(groups_cumulative, min_group_size = 15) ## End(Not run)## Not run: # Assuming you have cumulative group data: trajectories <- sniff_groups_lineage(groups_cumulative, min_group_size = 15) ## End(Not run)
Processes text data for structural topic modeling and performs topic number selection analysis, returning both the processed data and diagnostic plots.
sniff_groups_stm_prepare( groups, group_to_stm = "g01", search_topics = c(5:40, 45, 50, 55, 60), seed = 1234, cores = 1 )sniff_groups_stm_prepare( groups, group_to_stm = "g01", search_topics = c(5:40, 45, 50, 55, 60), seed = 1234, cores = 1 )
groups |
A list containing network data with a 'network' component |
group_to_stm |
Character string specifying which research group to process (default: 'g01') |
search_topics |
Numeric vector of topic numbers to evaluate (default: c(5:40, 45, 50, 55, 60)) |
seed |
Random seed for reproducibility (default: 1234) |
cores |
Number of CPU cores to use (default: 1) |
A list containing:
result: The searchK results object
plots: A list containing two ggplot objects (p1: metrics by K, p2: exclusivity vs coherence)
df_prep: Output from stm::textProcessor
df_doc: Output from stm::prepDocuments
df: Original filtered data
## Not run: output <- sniff_groups_stm_prepare(network_data) output$plots$p1 # View first plot output$result # Access search results ## End(Not run)## Not run: output <- sniff_groups_stm_prepare(network_data) output$plots$p1 # View first plot output$result # Access search results ## End(Not run)
Performs structural topic modeling on prepared text data and returns topic proportions and top documents for each topic.
sniff_groups_stm_run(groups_stm_prepare, k_topics = 12, n_top_documents = 50)sniff_groups_stm_run(groups_stm_prepare, k_topics = 12, n_top_documents = 50)
groups_stm_prepare |
A prepared STM object from |
k_topics |
Number of topics to model (default: 12) |
n_top_documents |
Number of top documents to each topic (default: 50) |
This function:
Fits an STM model with specified number of topics
Identifies top terms for each topic
Calculates topic proportions
Identifies top documents for each topic
A list containing:
topic_proportion2: Data frame with topic proportions and top terms
tab_top_documents: Data frame of top documents for each topic
## Not run: # Prepare data first stm_data <- sniff_groups_stm_prepare(network_data) # Run topic modeling stm_results <- sniff_groups_stm_run(stm_data, k_topics = 15) # Access results stm_results$topic_proportion2 # Topic proportions and terms stm_results$tab_top_documents # Top documents per topic ## End(Not run)## Not run: # Prepare data first stm_data <- sniff_groups_stm_prepare(network_data) # Run topic modeling stm_results <- sniff_groups_stm_run(stm_data, k_topics = 15) # Access results stm_results$topic_proportion2 # Topic proportions and terms stm_results$tab_top_documents # Top documents per topic ## End(Not run)
Identifies and extracts key terms from titles and abstracts of publications within different research groups using natural language processing techniques, and computes term statistics including TF-IDF scores.
sniff_groups_terms( net_groups, algorithm = "rake", phrase_pattern = "(A|N)*N(P+D*(A|N)*N)*", model_dir = tempdir(), n_cores = 1, show_progress = TRUE, n_terms = 15, min_freq = 2, digits = 4 )sniff_groups_terms( net_groups, algorithm = "rake", phrase_pattern = "(A|N)*N(P+D*(A|N)*N)*", model_dir = tempdir(), n_cores = 1, show_progress = TRUE, n_terms = 15, min_freq = 2, digits = 4 )
net_groups |
A list containing network data with publication information.
Must include elements: |
algorithm |
Term extraction algorithm to use. Options are:
|
phrase_pattern |
Regular expression pattern for phrase extraction when algorithm = "phrase" (default: "(A|N)N(P+D(A|N)N)") |
model_dir |
Directory where UDPipe models are stored (default: tempdir()) |
n_cores |
Number of CPU cores to use for parallel processing (default: 1) |
show_progress |
Logical indicating whether to show progress bar (default: TRUE) |
n_terms |
Number of top terms to return in summary table (default: 15) |
min_freq |
Minimum frequency threshold for terms (default: 2) |
digits |
Number of decimal places to round numerical values (default: 4) |
This function performs the following steps:
Validates input structure and parameters
Loads the UDPipe language model from the specified directory
Processes text data (titles and abstracts) for each group
Applies the selected term extraction algorithm (RAKE, PMI, or phrase patterns)
Computes term frequencies and TF-IDF scores
Returns ranked terms for each research group with comprehensive statistics
The function uses UDPipe for tokenization, lemmatization and POS tagging before term extraction. For phrase extraction, the default pattern finds noun phrases.
A list with two components:
terms_by_group: A named list (by group) of data frames containing extracted terms with statistics
terms_table: A summary tibble with top terms by frequency and TF-IDF for each group
## Not run: # Assuming groups is output from sniff_groups() terms <- sniff_groups_terms(groups, algorithm = "rake") # View terms for first group head(terms$terms_by_group[[1]]) # View summary table print(terms$terms_table) # Customized extraction with custom model directory net_groups_terms <- sniff_groups_terms(net_groups, algorithm = "phrase", model_dir = tempdir(), n_terms = 10, min_freq = 3, n_cores = 4 ) ## End(Not run)## Not run: # Assuming groups is output from sniff_groups() terms <- sniff_groups_terms(groups, algorithm = "rake") # View terms for first group head(terms$terms_by_group[[1]]) # View summary table print(terms$terms_table) # Customized extraction with custom model directory net_groups_terms <- sniff_groups_terms(net_groups, algorithm = "phrase", model_dir = tempdir(), n_terms = 10, min_freq = 3, n_cores = 4 ) ## End(Not run)
This function identifies and visualizes key citation routes within scientific networks by analyzing the most significant citation paths between publications. The algorithm implements the key-route search from the integrated main path analysis approach described in Liu & Lu (2012).
sniff_key_route( network, scope = "network", n_routes = 1, compact_gaps = FALSE, direction = "vertical" )sniff_key_route( network, scope = "network", n_routes = 1, compact_gaps = FALSE, direction = "vertical" )
network |
A network object of class |
scope |
Character string specifying the analysis scope. Must be either "network" (for full network analysis) or "groups" (for group-wise analysis of a grouped network) |
n_routes |
Positive integer specifying the number of key-route starting edges to use (default: 1). Each iteration selects the next-highest SPC edge as a new starting point and extends it into a full path. Higher values reveal auxiliary knowledge diffusion paths and divergence-convergence structures (see Liu & Lu, 2012, Figure 8). |
compact_gaps |
Logical. If TRUE, compresses the vertical axis by removing empty year gaps between publications. This produces shorter plots suitable for academic publications. Default is FALSE (chronological spacing). |
direction |
Character. Plot direction: |
The function implements the key-route search from Liu & Lu (2012):
Computes Search Path Count (SPC) for each citation link using an efficient O(V+E) algorithm based on topological sort. SPC measures how many source-to-sink paths traverse each link.
Selects the key-route: the link with the highest SPC value.
Searches forward from the end node of the key-route, greedily following the outgoing link with the highest SPC, until a sink is reached.
Searches backward from the start node of the key-route, greedily following the incoming link with the highest SPC, until a source is reached.
When n_routes > 1, the procedure is repeated: each iteration selects
the edge with the next-highest SPC as a new starting point and extends it
forward and backward. Routes can share nodes and edges, producing a
divergence-convergence-divergence structure that reveals how knowledge
flows through multiple sub-streams.
The SPC is computed as forward[u] * backward[v] for each edge (u, v),
where forward[u] counts paths from any source to u and backward[v]
counts paths from v to any sink (Batagelj, 2003). This guarantees the most
significant link is always included in the key-route path.
A list containing for each group:
plot - A ggplot2 object visualizing the key citation route(s)
data - A tibble with publication details (name, TI, AU, PY)
of nodes in the key route(s)
Liu JS, Lu LYY. An integrated approach for main path analysis: Development of the Hirsch index as an example. Journal of the American Society for Information Science and Technology. 2012;63(3):528-542. doi:10.1002/asi.21692
Batagelj V. Efficient algorithms for citation network analysis. University of Ljubljana, Institute of Mathematics, Physics and Mechanics, Department of Theoretical Computer Science, Preprint Series. 2003;41:897.
## Not run: # Single key-route (default) result <- sniff_key_route(my_network, scope = "network") # Multiple key-routes for richer structure result <- sniff_key_route(my_network, scope = "network", n_routes = 5) # Group-wise analysis grouped_network <- sniff_groups(data) result <- sniff_key_route(grouped_network, scope = "groups", n_routes = 3) # Access results for a specific group result$group_name$plot result$group_name$data ## End(Not run)## Not run: # Single key-route (default) result <- sniff_key_route(my_network, scope = "network") # Multiple key-routes for richer structure result <- sniff_key_route(my_network, scope = "network", n_routes = 5) # Group-wise analysis grouped_network <- sniff_groups(data) result <- sniff_key_route(grouped_network, scope = "groups", n_routes = 3) # Access results for a specific group result$group_name$plot result$group_name$data ## End(Not run)
Constructs different types of citation networks from bibliographic data imported
from Web of Science or OpenAlex using birddog's reading functions.
sniff_network(dataframe, type = "direct citation", external_references = FALSE)sniff_network(dataframe, type = "direct citation", external_references = FALSE)
dataframe |
A data frame imported via |
type |
Type of network to create. One of:
|
external_references |
Logical indicating whether to include external references (references not in the original dataset) as nodes in the network |
A tbl_graph object from the tidygraph package representing the citation network.
Node attributes include bibliographic information from the input data.
Other groups (stock):
sniff_components(),
sniff_groups(),
sniff_groups_attributes(),
sniff_groups_cumulative(),
sniff_groups_cumulative_citations(),
sniff_groups_hubs(),
sniff_groups_influence(),
sniff_groups_lineage()
## Not run: # Using OpenAlex data oa_data <- read_openalex("works.csv", format = "csv") net <- sniff_network(oa_data, type = "direct citation") # Using WoS data wos_data <- read_wos("savedrecs.txt") net <- sniff_network(wos_data, type = "bibliographic coupling", external_references = TRUE) ## End(Not run)## Not run: # Using OpenAlex data oa_data <- read_openalex("works.csv", format = "csv") net <- sniff_network(oa_data, type = "direct citation") # Using WoS data wos_data <- read_wos("savedrecs.txt") net <- sniff_network(wos_data, type = "bibliographic coupling", external_references = TRUE) ## End(Not run)
Decomposes the cumulative-clustering DAG into trajectories via trunk edges
(mutual dominance: v -> u where u is v's heaviest successor and v is
u's heaviest predecessor). Trunk edges form disjoint chains. A chain ending at
a final-year group node is a central trajectory (tr::<group>, one per final
group, reaches the final year); every other chain is absorbed (tr1 … trN),
merging into an absorber (central or another absorbed) where its head flows out
via a non-trunk edge. Absorption is transitive, forming a confluence forest.
sniff_trajectory_braid(x, min_group_size = 10, jaccard_min = 0.05, k_out = 2)sniff_trajectory_braid(x, min_group_size = 10, jaccard_min = 0.05, k_out = 2)
x |
A |
min_group_size, jaccard_min, k_out
|
Passed to |
A birddog_flow object: a classed list with trajectories (tibble:
traj_id, type ("central"/"absorbed"),
group (destination group; NA if extinct), start, end,
size (distinct papers), depth (0 = central), absorbed_into (absorber
traj_id, NA for central), absorption_year (NA for central), nodes
list-col), tree (edges child, parent, absorption_year), graph,
docs_per_group, last_year.
sniff_trajectory_channel(), sniff_trajectory_dag(),
sniff_trajectory_dynamics()
Other trajectory detection:
is_flow(),
sniff_trajectory_channel(),
sniff_trajectory_dag(),
subset.birddog_flow(),
validate_flow()
Recomputes the Citation Cycle Time (Kayal, 1999) from the documents in each
sniff_trajectory_braid() trajectory's own group-year nodes, one value per
year of the trajectory's life: per node-year, the median over its documents of
each document's median cited-reference age. A low or falling series marks a
fast-renewing knowledge stock, a diagnostic signal of emergence.
sniff_trajectory_cct(flow, references)sniff_trajectory_cct(flow, references)
flow |
A |
references |
A long data frame with columns |
This is the faithful, per-document counterpart of sniff_cct()
(which scores a static sniff_groups() partition by group-year): it follows the
trajectory's actual membership, so an absorbed tributary is scored on its own
documents rather than its destination group's.
A tibble, one row per trajectory: traj_id, type, group, and cct,
a list-column whose each cell is a tibble with columns year and cct (the
median citation age over that node-year's documents; NA when none of them
carry references).
Kayal AA, Waters RC (1999). An empirical evaluation of the technology cycle time indicator. IEEE Trans. Eng. Manag. 46(2):127-31.
sniff_cct(), sniff_trajectory_entropy(),
sniff_trajectory_dynamics(), sniff_trajectory_braid()
Other trajectory analysis:
sniff_trajectory_community(),
sniff_trajectory_confluence(),
sniff_trajectory_contribution(),
sniff_trajectory_destination(),
sniff_trajectory_dynamics(),
sniff_trajectory_emergence_owners(),
sniff_trajectory_entropy(),
sniff_trajectory_formation(),
sniff_trajectory_hubs(),
sniff_trajectory_self_sufficiency()
An alternative to sniff_trajectory_braid(). Instead of flow's local
mutual-dominance trunk edges, each node is routed to the successor on its
globally cheapest path to a final-year node, where edge cost is
-log(weight) (the Jaccard). The minimum-cost path is the maximum-product
Jaccard path (highest geometric-mean coherence). Final-year nodes root one
in-tree each (the watershed of a final community); the cheapest birth -> final
chain is the central trajectory (tr::<group>), the rest are absorbed
tributaries (tr1 … trN), mirroring the flow object exactly. "Flow" names the
object kind, not the algorithm, so the result is a birddog_flow and passes
validate_flow().
sniff_trajectory_channel(x, min_group_size = 10, jaccard_min = 0.05, k_out = 2)sniff_trajectory_channel(x, min_group_size = 10, jaccard_min = 0.05, k_out = 2)
x |
A |
min_group_size, jaccard_min, k_out
|
Passed to |
Adds one column to trajectories: latest_departure, the year of the most
recent birth that can reach the trajectory's tail by any time-respecting path
(Wu et al. 2014 latest-departure). For a central this is the freshest input
anywhere in the final community's ancestry; combined with start it separates
old-but-freshly-fed communities from genuinely young ones.
A birddog_flow object (see sniff_trajectory_braid()) with the extra
latest_departure column.
Wu H, Cheng J, Huang S, Ke Y, Lu Y, Xu Y (2014). Path Problems in Temporal Graphs. Proc. VLDB Endowment 7(9).
sniff_trajectory_braid(), validate_flow(), sniff_trajectory_dag()
Other trajectory detection:
is_flow(),
sniff_trajectory_braid(),
sniff_trajectory_dag(),
subset.birddog_flow(),
validate_flow()
Scores how content-coherent a sniff_trajectory_braid() / sniff_trajectory_channel()
partition is, using a silhouette over an independent content signal (shared
references or keywords) the detectors never optimized. Each group-year node is
profiled from its documents' features; node-to-node distance is 1 - Salton cosine; the silhouette is computed at two cluster resolutions: trajectory
(each backbone/tributary chain) and final_group (each central + its tributaries).
sniff_trajectory_coherence( flow, content, profile = "incremental", signals = names(content) )sniff_trajectory_coherence( flow, content, profile = "incremental", signals = names(content) )
flow |
A |
content |
Named list; each element a data frame with columns
|
profile |
|
signals |
Which |
An sniff_trajectory_coherence object: a list with nodes (one row per
node x signal x profile x resolution) and summary (one row per
signal x profile x resolution, with mean_sil, n_nodes, n_singletons,
n_excluded, coverage).
sniff_trajectory_comparison(), sniff_trajectory_braid(), sniff_trajectory_channel(),
validate_flow()
Other flow utilities:
sniff_trajectory_comparison()
Measures the FUSE community pillar (Carley et al., 2018) for every
sniff_trajectory_braid() trajectory: how many distinct authors stand behind
its documents, and how broad that authorship is per document. A raw author
count tracks trajectory size; authors_per_doc is the size-independent breadth
— a high value marks a lineage carried by many distinct contributors (a wide
community), a low value a lineage concentrated in a tight group.
sniff_trajectory_community(flow, authors)sniff_trajectory_community(flow, authors)
flow |
A |
authors |
A long data frame with columns |
This is the document-cluster analogue of the term-level community measure in
the emergence-indicator literature, where a term counts as emergent only when
used by more than one independent author. It complements
sniff_trajectory_dynamics(), which names the community pillar but scores it
only through document counts (recruitment, size).
A tibble, one row per trajectory: traj_id, type, group,
n_docs (distinct documents of the trajectory), n_authors (distinct
authors across them), authors_per_doc (n_authors / n_docs, the breadth;
NA for an empty trajectory).
sniff_trajectory_dynamics(), sniff_trajectory_hubs(),
sniff_trajectory_braid()
Other trajectory analysis:
sniff_trajectory_cct(),
sniff_trajectory_confluence(),
sniff_trajectory_contribution(),
sniff_trajectory_destination(),
sniff_trajectory_dynamics(),
sniff_trajectory_emergence_owners(),
sniff_trajectory_entropy(),
sniff_trajectory_formation(),
sniff_trajectory_hubs(),
sniff_trajectory_self_sufficiency()
Runs sniff_trajectory_coherence() on each named flow, across every content signal and the
chosen profile(s), and reports which detector produces the more
content-coherent partition, whether the signals agree, and the contested nodes
(those a pair of detectors place in different final groups).
sniff_trajectory_comparison( flows, content, profile = "incremental", signals = names(content) )sniff_trajectory_comparison( flows, content, profile = "incremental", signals = names(content) )
flows |
Named list of |
content |
Named list of |
profile |
Passed to |
signals |
Which |
A sniff_trajectory_comparison object: a list with summary (per
method x signal x profile x resolution), verdict (best_method, delta
per signal x profile x resolution), agreement (do all signals pick the same
best_method, per profile x resolution), and contested (nodes the
detectors place in different final groups, with each method's final_group
and silhouette, plus the closer_method;
NULL unless exactly two flows are given).
Other flow utilities:
sniff_trajectory_coherence()
Turns a sniff_trajectory_braid() object into the data a trajectory confluence
needs: one rivers row per trajectory (with a per-year size curve) and one
confluences row per child to parent merge edge (papers transferred, the
first year a transferred paper was published, the handoff year, and the
cumulative-inflow curve). Every edge of the flow tree is kept; thresholds are
applied later by plot_trajectory_confluence(), so a slider can re-filter
without recomputing.
sniff_trajectory_confluence( x, min_group_size = 10, jaccard_min = 0.05, k_out = 2 )sniff_trajectory_confluence( x, min_group_size = 10, jaccard_min = 0.05, k_out = 2 )
x |
A |
min_group_size, jaccard_min, k_out
|
Passed to |
A list with:
Tibble, one row per trajectory: traj_id, type
("central"/"absorbed"), central (destination group cNgN), size,
start, handoff_year (= last_year for centrals, else absorption year),
depth, parent (absorber traj_id, NA for central), and size_curve
(list-col of tibble(year, size), the trajectory's community size per
year).
Tibble, one row per child to parent merge: child,
parent, n (papers transferred), cohort_size (the child's size),
first_feed_year (publication year of the first transferred paper),
handoff_year, and inflow_curve (list-col of tibble(year, size)).
Tibble, one row per (absorbed trajectory, final group) it
fed: traj_id, g_final, n (papers of the trajectory's terminal cohort
that ended up in g_final). A trajectory usually appears under several
finals, so this is the multi-destination split the forest collapses to one.
The central group ids (cNgN) in mixed_sort() order.
Final year of the analysis.
sniff_trajectory_braid(), plot_trajectory_confluence()
Other trajectory analysis:
sniff_trajectory_cct(),
sniff_trajectory_community(),
sniff_trajectory_contribution(),
sniff_trajectory_destination(),
sniff_trajectory_dynamics(),
sniff_trajectory_emergence_owners(),
sniff_trajectory_entropy(),
sniff_trajectory_formation(),
sniff_trajectory_hubs(),
sniff_trajectory_self_sufficiency()
Selects a source trajectory's node in a specific year and returns its
documents, flagging which of them belong to the target trajectory's
final-year community. This is the fine-grained, per-document view of the
inter-trajectory contribution that sniff_trajectory_destination() aggregates:
where destination splits a terminal cohort across every final group,
contribution answers "which papers of source, as they stood in year,
consolidate into this one target".
sniff_trajectory_contribution(flow, source, year, target)sniff_trajectory_contribution(flow, source, year, target)
flow |
A |
source |
Trajectory id whose year-node is read, typically an intermediate
|
year |
Integer publication year. |
target |
Trajectory id whose final-year community is the contribution
basis, typically a central |
The result is document ids only (plus the contribution flag), so it composes
with any downstream join: keywords (via the corpus DE field or
sniff_groups_keywords()), authors, citations, or STM topics. Filter on
in_target for the contributed set.
A tibble with one row per document in source's year node:
source, target, year, document_id, and in_target (logical: the
document is in target's final-year community). The source/target/year
columns are constant, so results for several (source, year, target) triples
row-bind cleanly. Summarise with one line, e.g.
dplyr::summarise(x, n = dplyr::n(), contributed = sum(in_target)).
sniff_trajectory_destination(), sniff_trajectory_self_sufficiency(),
sniff_groups_keywords()
Other trajectory analysis:
sniff_trajectory_cct(),
sniff_trajectory_community(),
sniff_trajectory_confluence(),
sniff_trajectory_destination(),
sniff_trajectory_dynamics(),
sniff_trajectory_emergence_owners(),
sniff_trajectory_entropy(),
sniff_trajectory_formation(),
sniff_trajectory_hubs(),
sniff_trajectory_self_sufficiency()
Reconstructs, from docs_per_group alone, the full temporal DAG of
cluster-year nodes: vertices are every cluster whose distinct-document count is
at least min_group_size (isolated nodes included), edges are consecutive-year
Jaccard links (top k_out per source, weight >= jaccard_min). Each node is
labelled with the final group its heaviest-successor walk reaches
(terminal_group) and whether it is a birth (no strong predecessor). This DAG
is the first-class object the soft trajectories and group-formation views are
read from.
sniff_trajectory_dag( x, min_group_size = 10, jaccard_min = 0.05, k_out = 2, similarity = c("overlap", "coupling"), references = NULL )sniff_trajectory_dag( x, min_group_size = 10, jaccard_min = 0.05, k_out = 2, similarity = c("overlap", "coupling"), references = NULL )
x |
The membership tibble |
min_group_size |
Minimum distinct-document count for a node (default 10). |
jaccard_min |
Minimum Jaccard weight to keep an edge (default 0.05). |
k_out |
Max outgoing edges kept per source node (default 2). |
similarity |
How to weight consecutive-year edges: |
references |
When |
A birddog_dag object: a classed list with graph (scored igraph DAG
with vertex attrs year, size, group, is_birth, terminal_group,
plus doc_ids), nodes (tidy tibble name, year, group, size,
is_birth, terminal_group), edges (from, to, weight,
documents), births (character vector of birth node names), last_year,
and the de-duplicated docs_per_group.
Other trajectory detection:
is_flow(),
sniff_trajectory_braid(),
sniff_trajectory_channel(),
subset.birddog_flow(),
validate_flow()
Returns the destination of source's terminal cohort: its per-final-group
split and dormant share, plus its immediate absorber and ultimate
destination.
sniff_trajectory_destination(x, ...)sniff_trajectory_destination(x, ...)
x |
A |
... |
|
A list (target, target_info, destination, cohort_size,
dormant_share, continuation_traj, last_year).
sniff_trajectory_braid(), sniff_trajectory_self_sufficiency()
Other trajectory analysis:
sniff_trajectory_cct(),
sniff_trajectory_community(),
sniff_trajectory_confluence(),
sniff_trajectory_contribution(),
sniff_trajectory_dynamics(),
sniff_trajectory_emergence_owners(),
sniff_trajectory_entropy(),
sniff_trajectory_formation(),
sniff_trajectory_hubs(),
sniff_trajectory_self_sufficiency()
Characterizes each sniff_trajectory_braid() trajectory along the evolutionary
states of the method (emergence, convergence, divergence, dormancy), grounding
the four emergence pillars (novelty, growth, community, persistence) in two
complementary lenses tied to the flow model.
sniff_trajectory_dynamics( flow, thresholds = NULL, k = 1, growth_window = 3, novelty_window = 5, winsorize = 3, cct = NULL, entropy = NULL, hubs = NULL )sniff_trajectory_dynamics( flow, thresholds = NULL, k = 1, growth_window = 3, novelty_window = 5, winsorize = 3, cct = NULL, entropy = NULL, hubs = NULL )
flow |
A |
thresholds |
A |
k |
Robust deviations for the data-driven thresholds when |
growth_window |
Curve points over which |
novelty_window |
Years counted as recent for |
winsorize |
Cap each pillar's robust z-score at |
cct |
Optional |
entropy |
Optional |
hubs |
Optional |
Forward lens (all trajectories). growth_rate and doubling_time (an
exponential fit of the recent size curve), novelty (share of documents that
arrived recently), recruitment (net documents gained over the lifespan, the
power of attraction), emergence_density (staying power: the share of the
lineage's year-steps still growing at the emergence rate, after Carley et al.
2017), and age/size (persistence and community).
Backward lens (absorbed trajectories only). Where the terminal cohort goes,
via sniff_trajectory_destination(): dest_entropy (normalized Shannon
entropy of the destination split) and dormant_share (share dropping out).
Inflow lens (central trajectories only). attraction_inflow, the documents
of the final community delivered by absorbed tributaries (the consolidating side
of convergence), from sniff_trajectory_self_sufficiency().
Reach lens (all trajectories). reach_ratio, the share of the future field
a lineage's origin seeds – its birth node's forward temporal reach in the DAG
over all nodes born later (after the temporal-reachability of Marino & Silva
2023). Structural downstream influence, distinct from size and, via the ratio,
from age.
Classification uses two axes: a life-cycle phase for living (central)
trajectories – emergence / maturity / dormancy(stall) by growth and novelty –
and a terminal fate for declining (absorbed) ones – convergence /
divergence / dormancy(death) by destination. state is phase for centrals and
fate for absorbed; phase and fate are kept separate so the two senses of
dormancy (a stalled living core vs a dead cohort) stay distinguishable. The
emergence_index (a within-central combination of growth, novelty and
recruitment) is computed for living trajectories only, so an absorbed lineage
that grew before dying is never ranked as emerging. Because recruitment is an
extensive (size-carrying) pillar, the full index partly tracks community size;
emergence_index_intensive drops it (growth and novelty only) for a rate-based
ranking that does not reward a large core for its volume alone.
A tibble, one row per trajectory, sorted by descending
emergence_index (absorbed last): traj_id, type, group, start, end,
age, size, growth_rate, doubling_time, novelty, recruitment,
emergence_density, attraction_inflow, dest_entropy, dormant_share,
phase, fate,
state, emergence_index, emergence_index_intensive (the same index
without the extensive recruitment pillar), reach_ratio, plus any
cct / keyword_entropy (per-year list-columns) and hubs columns.
sniff_trajectory_braid(), sniff_trajectory_destination(),
sniff_trajectory_self_sufficiency(), sniff_trajectory_cct(),
sniff_trajectory_entropy(), sniff_trajectory_hubs(),
fixed_state_thresholds()
Other trajectory analysis:
sniff_trajectory_cct(),
sniff_trajectory_community(),
sniff_trajectory_confluence(),
sniff_trajectory_contribution(),
sniff_trajectory_destination(),
sniff_trajectory_emergence_owners(),
sniff_trajectory_entropy(),
sniff_trajectory_formation(),
sniff_trajectory_hubs(),
sniff_trajectory_self_sufficiency()
The actor-level companion to sniff_trajectory_dynamics(), after the player
scoring of Garner et al. (2017): it credits each author with the emergence of
the living trajectories their documents belong to. An author who publishes many
documents inside high-emergence_index centrals is an owner of the field's
emergence; one whose work sits in mature or absorbed lineages is not.
sniff_trajectory_emergence_owners( flow, dynamics, authors, min_docs = 1, by_trajectory = FALSE, top_n = NULL )sniff_trajectory_emergence_owners( flow, dynamics, authors, min_docs = 1, by_trajectory = FALSE, top_n = NULL )
flow |
A |
dynamics |
A |
authors |
A long data frame with columns |
min_docs |
Drop authors with fewer than this many credited documents:
summed across trajectories when aggregated, or within the trajectory when
|
by_trajectory |
If |
top_n |
Keep only the highest-scoring authors – overall (by |
For every central trajectory with a defined emergence_index (the living
population), each author is credited emergence_index(t) per distinct document
they hold in t. Absorbed lineages carry no emergence_index and are skipped,
so a contributor to a dead tributary is never scored as owning emergence.
where is author 's distinct documents in trajectory
. Total favours prolific owners; Norm (per square-root of output,
Garner's normalization) surfaces high-intensity authors with fewer papers.
With by_trajectory = TRUE the per-trajectory term is returned
instead of being summed away: one row per (trajectory, author), so you can read
which authors dominate each living trajectory rather than the field as a
whole. top_n then trims to the leading authors per trajectory.
A tibble.
When by_trajectory = FALSE (default): one row per author, sorted by
descending total – author, total (emergence ownership), ndocs
(credited document-trajectory incidences), norm (total / sqrt(ndocs)).
When by_trajectory = TRUE: one row per trajectory-author, sorted by
trajectory then descending contribution – traj_id, group, author,
ndocs (the author's distinct documents in the trajectory, ),
emergence_index (the trajectory's weight), contribution
(emergence_index * ndocs, the author's share of that trajectory's
emergence), and share (ndocs over the trajectory's distinct documents –
the author's coverage of its papers).
sniff_trajectory_dynamics(), sniff_trajectory_community(),
sniff_trajectory_contribution()
Other trajectory analysis:
sniff_trajectory_cct(),
sniff_trajectory_community(),
sniff_trajectory_confluence(),
sniff_trajectory_contribution(),
sniff_trajectory_destination(),
sniff_trajectory_dynamics(),
sniff_trajectory_entropy(),
sniff_trajectory_formation(),
sniff_trajectory_hubs(),
sniff_trajectory_self_sufficiency()
Recomputes, from the documents in each sniff_trajectory_braid() trajectory's
own group-year nodes, the normalized Shannon entropy (Pielou's evenness J') of
their keyword distribution, one value per year of the trajectory's life. A
falling series marks thematic convergence, a rising one diversification. Unlike
sniff_entropy(), which scores a static sniff_groups() partition by
group-year, this follows the trajectory's actual membership, so an absorbed
tributary is scored on its own documents rather than its destination group's.
sniff_trajectory_entropy(flow, keywords)sniff_trajectory_entropy(flow, keywords)
flow |
A |
keywords |
A long data frame with columns |
This is keyword entropy (thematic diversity), distinct from the dynamics
dest_entropy (the dispersion of an absorbed cohort across destination groups);
the dynamics column is named keyword_entropy to keep the two apart.
A tibble, one row per trajectory: traj_id, type, group, and
keyword_entropy, a list-column whose each cell is a tibble with columns
year and keyword_entropy (Pielou's J' over that node-year's documents, in
[0, 1]; 0 for a single keyword, NA when the year's documents carry none).
sniff_entropy(), sniff_trajectory_cct(), sniff_trajectory_dynamics(),
sniff_trajectory_braid()
Other trajectory analysis:
sniff_trajectory_cct(),
sniff_trajectory_community(),
sniff_trajectory_confluence(),
sniff_trajectory_contribution(),
sniff_trajectory_destination(),
sniff_trajectory_dynamics(),
sniff_trajectory_emergence_owners(),
sniff_trajectory_formation(),
sniff_trajectory_hubs(),
sniff_trajectory_self_sufficiency()
Returns the target trajectory's direct tributaries: its children in the
confluence tree, the trajectories whose terminal cohort was absorbed into
target. The result is the formation shape that
plot_trajectory_formation() renders.
sniff_trajectory_formation(x, ...)sniff_trajectory_formation(x, ...)
x |
A |
... |
|
A formation list (target, target_info, feeders, total_inflow,
last_year). The feeders carry n_dest (the feeder's documents that end
in the target's final community) and target_info carries final_size (the
target's final-year size); plot_trajectory_formation() uses these
for its labels.
sniff_trajectory_braid(), plot_trajectory_formation()
Other trajectory analysis:
sniff_trajectory_cct(),
sniff_trajectory_community(),
sniff_trajectory_confluence(),
sniff_trajectory_contribution(),
sniff_trajectory_destination(),
sniff_trajectory_dynamics(),
sniff_trajectory_emergence_owners(),
sniff_trajectory_entropy(),
sniff_trajectory_hubs(),
sniff_trajectory_self_sufficiency()
Summarizes the document-level hub roles of sniff_groups_hubs() (the
Guimera–Amaral within-module z-score Zi and participation coefficient Pi)
over the documents of every sniff_trajectory_braid() trajectory. Provincial
hubs (low Pi) keep a lineage self-contained, consistent with convergence /
paradigmatic homogeneity; connector and boundary-spanning hubs (high Pi)
bridge across groups, consistent with divergence / branching.
sniff_trajectory_hubs(flow, hubs)sniff_trajectory_hubs(flow, hubs)
flow |
A |
hubs |
A |
A tibble, one row per trajectory: traj_id, type, group, n_docs
(documents of the trajectory), mean_Zi, mean_Pi, hub_share (share that
are hubs), connector_share (share that are connector/boundary hubs R6/R7,
the bridging signal), provincial_share (share that are provincial hubs R5).
sniff_groups_hubs(), sniff_trajectory_dynamics(),
sniff_trajectory_braid()
Other trajectory analysis:
sniff_trajectory_cct(),
sniff_trajectory_community(),
sniff_trajectory_confluence(),
sniff_trajectory_contribution(),
sniff_trajectory_destination(),
sniff_trajectory_dynamics(),
sniff_trajectory_emergence_owners(),
sniff_trajectory_entropy(),
sniff_trajectory_formation(),
sniff_trajectory_self_sufficiency()
Returns each central trajectory's endogenous fraction: the share of its
final-year community that was NOT delivered by any absorbed tributary
(1 = fully endogenous, ~0 = a confluence of tributaries). Inflow follows the
same destination semantics as plot_trajectory_confluence() and
sniff_trajectory_destination(): a document counts as imported when it sits
in the central's final community and in the terminal cohort of some
absorbed tributary, counted once even if several tributaries carried it, and
regardless of which central the tributary is dominantly assigned to – so
secondary inflow (tributaries owned by other centrals) is included, not only
the central's own feeders.
sniff_trajectory_self_sufficiency(x, ...)sniff_trajectory_self_sufficiency(x, ...)
x |
A |
... |
|
A tibble (central, group, final_size, inflow,
self_sufficiency), sorted by descending self_sufficiency.
sniff_trajectory_braid(), sniff_trajectory_destination()
Other trajectory analysis:
sniff_trajectory_cct(),
sniff_trajectory_community(),
sniff_trajectory_confluence(),
sniff_trajectory_contribution(),
sniff_trajectory_destination(),
sniff_trajectory_dynamics(),
sniff_trajectory_emergence_owners(),
sniff_trajectory_entropy(),
sniff_trajectory_formation(),
sniff_trajectory_hubs()
Filters the trajectories of a flow object while keeping it a valid flow:
$tree and absorbed_into are reattached by transitive bypass (a kept
child points to its nearest kept ancestor; NA if none survives) and
$docs_per_group follows the kept nodes. The result is a view, not a
census: the call is recorded in attr(, "pruned") and shown by print().
Dispatches for any detector whose result inherits from birddog_flow
(the contract class), so future detection methods are subsettable for free.
## S3 method for class 'birddog_flow' subset(x, subset, target = NULL, ...)## S3 method for class 'birddog_flow' subset(x, subset, target = NULL, ...)
x |
A |
subset |
Logical expression on the columns of |
target |
Optional character vector of |
... |
Ignored (base generic compatibility). |
An object of the same class as x, passing validate_flow().
sniff_trajectory_braid(), validate_flow()
Other trajectory detection:
is_flow(),
sniff_trajectory_braid(),
sniff_trajectory_channel(),
sniff_trajectory_dag(),
validate_flow()
The authoritative checker of the flow contract (trajectories, tree,
graph, docs_per_group, last_year). "Flow" names the object kind, not
the algorithm: any detector – sniff_trajectory_braid() today, alternative
sniff_trajectory_<algo>() detectors in the future – must return an object
that passes this validator and inherits the birddog_flow class.
validate_flow(x)validate_flow(x)
x |
The object to validate, typically a |
Beyond the column schema, the checked invariants are the ones that otherwise
fail silently: every trajectory node label must be a docs_per_group$group_id
key and a graph vertex (the triple key documents and analyses share), the
graph must carry the quantity_papers / prop_tracked_intra_group vertex
attributes the line plots read, absorbed_into must reference existing
trajectories without forming a cycle, each final group must have exactly one
central present at last_year, central ids must use the tr:: convention,
and start/end must match the node years.
x, invisibly, when valid; otherwise an error listing every violation.
sniff_trajectory_braid(), is_flow()
Other trajectory detection:
is_flow(),
sniff_trajectory_braid(),
sniff_trajectory_channel(),
sniff_trajectory_dag(),
subset.birddog_flow()