{talib}: Technical Analysis using R

talib logo

{talib} is a new R package built on TA-Lib, which is now available on CRAN. The R-package is targeted at individuals and, perhaps, institutions who, in some form or the other, interacts with the financial markets using technical analysis.

The library is built with minimal dependencies for long-term stability and freedom in mind. All functions are built around data.frame– and matrix-classes which are portable to all other data-containers with minimal effort.

Everything in the library is built ‘bottom-up’ for maximum speed and memory efficiency. Each indicator interacts directly with R’s C API via .Call().

In this blog post I will give a brief introduction to the interface and the most important aspects of the package. The library also includes static and interactive financial charts, which will be covered in a different post.

A quick introduction to the interface

In this section I will briefly introduce the most important aspects of the function calls, formals and how <NA> are handled. Below is a simple starting point; calculating the Bollinger Bands for Bitcoin:

tail(
  talib::bollinger_bands(
    talib::BTC
  )
)
#>                     UpperBand MiddleBand LowerBand
#> 2024-12-26 01:00:00 100487.38   96698.61  92909.83
#> 2024-12-27 01:00:00 100670.65   96512.96  92355.27
#> 2024-12-28 01:00:00 100632.13   96581.91  92531.69
#> 2024-12-29 01:00:00  99628.77   95576.60  91524.43
#> 2024-12-30 01:00:00  96403.53   94231.31  92059.09
#> 2024-12-31 01:00:00  95441.13   93774.23  92107.34

In itself a simple call. Below are the formals:

str(formals(talib::bollinger_bands))
#> Dotted pair list of 8
#>  $ x        : symbol 
#>  $ cols     : symbol 
#>  $ ma       : language SMA(n = 5)
#>  $ sd       : num 2
#>  $ sd_down  : symbol 
#>  $ sd_up    : symbol 
#>  $ na.bridge: logi FALSE
#>  $ ...      : symbol

All functions share the same signature x, cols, na.bridge and ..., while everything else is indicator-specific. The cols-argument is missing, but has a default value hard-coded against the upstream TA-Lib – this is also true for the remaining indicators. The cols-argument accepts a one-sided formula as follows:

tail(
  talib::bollinger_bands(
    talib::BTC,
    cols = ~close
  )
)
#>                     UpperBand MiddleBand LowerBand
#> 2024-12-26 01:00:00 100487.38   96698.61  92909.83
#> 2024-12-27 01:00:00 100670.65   96512.96  92355.27
#> 2024-12-28 01:00:00 100632.13   96581.91  92531.69
#> 2024-12-29 01:00:00  99628.77   95576.60  91524.43
#> 2024-12-30 01:00:00  96403.53   94231.31  92059.09
#> 2024-12-31 01:00:00  95441.13   93774.23  92107.34

In this case the resulting data.frame is the same as above, as the default column for which the bands are calculated is the closing price of the asset.

All indicators are wrapped by model.frame() and its functionality can be accessed via ... as follows:

tail(
  talib::bollinger_bands(
    talib::BTC,
    cols = ~close,
    subset = 1:nrow(talib::BTC) %in% 1:100
  )
)
#>                     UpperBand MiddleBand LowerBand
#> 2024-04-04 02:00:00  72605.20   68193.82  63782.44
#> 2024-04-05 02:00:00  70646.91   67502.88  64358.84
#> 2024-04-06 02:00:00  70087.36   67344.94  64602.52
#> 2024-04-07 02:00:00  70475.41   68122.29  65769.16
#> 2024-04-08 02:00:00  71820.87   69249.88  66678.89
#> 2024-04-09 02:00:00  71848.20   69372.65  66897.09

Here we only calculate the indicator on a subset of the BTC. While this may seem like a redundant ‘wow’-feature at first glance, its primary justification is in the charting interface where only parts of an indicator is of visual interest.

The <NA>-handling in {talib} works a bit differently than na.rm. Before I demonstrate this, we add some missing values randomly to BTC

BTC <- talib::BTC
BTC$close[sample(1:100, size = 20)] <- NA

The naive approach is to calculate the indicator directly:

tail(
  talib::bollinger_bands(
    BTC
  )
)
#>                     UpperBand MiddleBand LowerBand
#> 2024-12-26 01:00:00        NA         NA        NA
#> 2024-12-27 01:00:00        NA         NA        NA
#> 2024-12-28 01:00:00        NA         NA        NA
#> 2024-12-29 01:00:00        NA         NA        NA
#> 2024-12-30 01:00:00        NA         NA        NA
#> 2024-12-31 01:00:00        NA         NA        NA

Which returns <NA> for all values. This is the default behaviour. The function faithfully returns the full object with the same number of rows – filled with <NA>-values.

To avoid this you can set na.bridge = TRUE as follows:

tail(
  object <- talib::bollinger_bands(
    BTC,
    na.bridge = TRUE
  )
)
#>                     UpperBand MiddleBand LowerBand
#> 2024-12-26 01:00:00 100487.38   96698.61  92909.83
#> 2024-12-27 01:00:00 100670.65   96512.96  92355.27
#> 2024-12-28 01:00:00 100632.13   96581.91  92531.69
#> 2024-12-29 01:00:00  99628.77   95576.60  91524.43
#> 2024-12-30 01:00:00  96403.53   94231.31  92059.09
#> 2024-12-31 01:00:00  95441.13   93774.23  92107.34

Again, with the same number of rows as BTC. This behaviour is true for all functions: N-rows in, N-rows out. What na.bridge is doing under the hood, is to extract all <NA>-values, calculate the indicator and then re-adds them in their original position.

Installation

{talib} is finally on CRAN, and can be installed as follows:

install.packages("talib")

It can also be built from source with additional CMake-flags:

install.packages(
  "talib",
  type = "source",
  configure.args = "-O3 -march=native"
)

Contributing and submitting bug-reports

{talib} is still in its early stage so contributions, even if small, bug-reports, suggestions and critiques are gratefully accepted.

Visit the repository here: https://github.com/serkor1/ta-lib-R.

Created on 2026-04-24 with reprex v2.1.1

New R Package {bdlnm} Released on CRAN: Bayesian Distributed Lag Non-Linear Models in R via INLA

CRAN, GitHub

TL;DR: {bdlnm} brings Bayesian Distributed Lag Non-Linear Models (B-DLNMs) to R using INLA, allowing to model complex DLNMs, quantify uncertainty, and produce rich visualizations.

Background

Climate change is increasing exposure to extreme environmental conditions such as heatwaves and air pollution. However, these exposures rarely have immediate effects. For example:

    • A heatwave today may increase mortality several days later
    • Air pollution can have cumulative and delayed impacts

Distributed Lag Non-Linear Models (DLNMs) are the standard framework for studying these effects. They simultaneously model:

    • How risk changes with exposure level (exposure-response)
    • How risk evolve over time (lag-response)

Usually in the presence of non-linear effects, splines are used to define these two relationships. These two basis are then combined through a cross-basis function. 

As datasets become larger and more complex (e.g., studies with different regions and longer time periods), classical approaches show limitations. Bayesian DLNMs extend this framework by:

    • Supporting more flexible model structures
    • Providing full posterior distributions
    • Enabling richer uncertainty quantification

The new {bdlnm} package extends the framework of the {dlnm} package to a Bayesian setting, using Integrated Nested Laplace Approximation (INLA), a fast alternative to MCMC for Bayesian inference.

Installing and loading the package

As of March 2026, the package is available on CRAN:

install.packages("bdlnm")
library(bdlnm)

At least the stable version of INLA 23.4.24 (or newest) must be installed beforehand. You can install the newest stable INLA version by:

install.packages(
  "INLA",
  repos = c(
    getOption("repos"),
    INLA = "https://inla.r-inla-download.org/R/stable"
  ),
  dep = TRUE
)

Now, let’s load all the libraries we will need for this short tutorial:

Load required libraries
# DLNMs and splines
library(dlnm)
library(splines)

# Data manipulation
library(dplyr)
library(reshape2)
library(stringr)
library(lubridate)

# Visualization
library(ggplot2)
library(gganimate)
library(ggnewscale)
library(patchwork)
library(scales)
library(plotly)

# Tables
library(gt)

# Execution time
library(tictoc)

Hands-on example

We use the built-in london dataset with daily temperature and mortality (age 75+) from 2000-2012.

Before fitting any model, it is useful to explore the raw data. This plot shows daily mean temperature and mortality for the 75+ age group in London from 2000 to 2012, providing a first look at the time series we are trying to model:

col_mort <- "#2f2f2f"
col_temp <- "#8e44ad"

# Scaling parameters
a <- (max(london$mort_75plus) - min(london$mort_75plus)) /
  (max(london$tmean) - min(london$tmean))
b <- min(london$mort_75plus) - min(london$tmean) * a

p <- ggplot(london, aes(x = yday(date))) +
  geom_line(
    aes(y = a * tmean + b, color = "Mean Temperature"),
    linewidth = 0.4
  ) +
  geom_line(
    aes(y = mort_75plus, color = "Daily Mortality (+75 years)"),
    linewidth = 0.4
  ) +
  facet_wrap(~year, ncol = 3) +
  scale_y_continuous(
    name = "Daily Mortality (+75 years)",
    breaks = seq(0, 225, by = 50),
    sec.axis = sec_axis(
      name = "Mean Temperature (°C)",
      transform = ~ (. - b) / a,
      breaks = seq(-10, 30, by = 10)
    )
  ) +
  scale_x_continuous(
    breaks = yday(as.Date(paste0(
      "2000-",
      c("01", "03", "05", "07", "09", "11"),
      "-01"
    ))),
    labels = c("Jan", "Mar", "May", "Jul", "Sep", "Nov"),
    expand = c(0.01, 0)
  ) +
  scale_color_manual(
    values = c(
      "Daily Mortality (+75 years)" = col_mort,
      "Mean Temperature" = col_temp
    )
  ) +
  labs(x = NULL, color = NULL) +
  guides(color = "none") +
  theme_minimal() +
  theme(
    axis.title.y.left = element_text(
      color = col_mort,
      face = "bold",
      margin = margin(r = 8)
    ),
    axis.title.y.right = element_text(
      color = col_temp,
      face = "bold",
      margin = margin(l = 8)
    ),
    axis.text.y.left = element_text(color = col_mort),
    axis.text.y.right = element_text(color = col_temp)
  ) +
  transition_reveal(as.numeric(date))

animate(p, nframes = 300, fps = 10, end_pause = 100)



Model overview

Conceptually, DLNMs model:

    • Exposure-response: how risk changes with exposure level

    • Lag-response: how risk unfold over time

A typical model is:

Yt Poisson ( μt )
log ( μt ) = α + cb ( xt , , x tL ) · β + k
γk u kt

where:

    • α is the intercept
    • cb(·) is the cross-basis function, defining both the exposure-response and lag-response relationships
    • β are the coefficients associated with the cross-basis terms
    • ukt are time-varying covariates with corresponding coefficients γk

Model specification & setup

Before fitting the model, we have to define the spline-based exposure-response and lag-response functions using the {dlnm} package.

For our example, we will use common specifications in the literature in temperature-mortality studies:

    • Exposure-response: natural spline with three knots placed at the 10th, 75th, and 90th percentiles of daily mean temperature

    • Lag-response: natural spline with three knots equally spaced on the log scale up to a maximum lag of 21 days

# Exposure-response and lag-response spline parameters
dlnm_var <- list(
  var_prc = c(10, 75, 90),
  var_fun = "ns",
  lag_fun = "ns",
  max_lag = 21,
  lagnk = 3
)

# Cross-basis parameters
argvar <- list(
  fun = dlnm_var$var_fun,
  knots = quantile(london$tmean, dlnm_var$var_prc / 100, na.rm = TRUE),
  Bound = range(london$tmean, na.rm = TRUE)
)

arglag <- list(
  fun = dlnm_var$lag_fun,
  knots = logknots(dlnm_var$max_lag, nk = dlnm_var$lagnk)
)

# Create crossbasis
cb <- crossbasis(london$tmean, lag = dlnm_var$max_lag, argvar, arglag)

As it’s commonly done in these scenarios, we will also control for the seasonality of the mortality time series using a natural spline with 8 degrees of freedom per year, which flexibly controls for long-term and seasonal trends in mortality:

seas <- ns(london$date, df = round(8 * length(london$date) / 365.25))

Finally, we also have to define the temperature values for which predictions will be generated:

temp <- round(seq(min(london$tmean), max(london$tmean), by = 0.1), 1)

Fit the model

Fit the previously defined Bayesian DLNM using the function bdlnm(). We draw 1000 samples from the posterior distribution and set a seed for reproducibility:

tictoc::tic()
mod <- bdlnm(
  mort_75plus ~ cb + factor(dow) + seas,
  data = london,
  family = "poisson",
  sample.arg = list(n = 1000, seed = 5243)
)
tictoc::toc()
8.33 sec elapsed

Internally, bdlnm():

    • fits the model using INLA

    • returns posterior samples for all parameters

Minimum mortality temperature

We estimate the minimum mortality temperature (MMT), defined as the temperature at which the overall mortality risk is minimized. This optimal value will later be used to center the estimated relative risks.

tictoc::tic()
mmt <- optimal_exposure(mod, exp_at = temp)
tictoc::toc()
7.3 sec elapsed

The Bayesian framework, compared to the frequentist perspective, provides directly the full posterior distribution of the MMT. It is useful to inspect this distribution to assess whether multiple candidate optimal exposure values exist and to verify that the median provides a reasonable centering value:

ggplot(as.data.frame(mmt$est), aes(x = mmt$est)) +
  geom_histogram(
    fill = "#A8C5DA",
    bins = length(unique(mmt$est)),
    alpha = 0.6,
    color = "white"
  ) +
  geom_density(
    aes(y = after_stat(density) * length(mmt$est) / length(unique(mmt$est))),
    color = "#2E5E7E",
    linewidth = 1.2,
    adjust = 2 # <-- key change: higher = smoother
  ) +
  geom_vline(
    xintercept = mmt$summary["0.5quant"],
    color = "#2E5E7E",
    linewidth = 1.1,
    linetype = "dashed"
  ) +
  scale_x_continuous(breaks = seq(min(mmt$est), max(mmt$est), by = 0.1)) +
  labs(x = "Temperature (°C)", y = "Frequency") +
  theme_minimal()

The posterior distribution of the MMT is concentrated around 18.9ºC and is unimodal, so the median is a stable centering value for the relative risk estimates.

The posterior distribution of the MMT can also be visualized directly using the package’s plot() method: plot(mmt).

Predict exposure-lag-response effects

We predict the exposure-lag-response association between temperature and mortality from the fitted model at the supplied temperature grid:

cen <- mmt$summary[["0.5quant"]]
tictoc::tic()
cpred <- bcrosspred(mod, exp_at = temp, cen = cen)
tictoc::toc()
6.83 sec elapsed

Centering at the MMT means that relative risks (RR) are interpeted relative to this optimal temperature with minimum mortality.

Several visualizations can be produced from these predictions. While simpler visualizations can be created using the package’s plot() method, here we will use fancier ggplot2 visualizations:

3D exposure-lag-response surface

We can plot the full exposure-lag-response association using a 3-D surface:

matRRfit_median <- cpred$matRRfit.summary[,, "0.5quant"]
x <- rownames(matRRfit_median)
y <- colnames(matRRfit_median)
z <- t(matRRfit_median)

zmin <- min(z, na.rm = TRUE)
zmax <- max(z, na.rm = TRUE)
mid <- (1 - zmin) / (zmax - zmin)

plot_ly() |>
  add_surface(
    x = x,
    y = y,
    z = z,
    surfacecolor = z,
    cmin = zmin,
    cmax = zmax,
    colorscale = list(
      c(0, "#00696e"),
      c(mid * 0.5, "#80c8c8"),
      c(mid, "#f5f0e8"),
      c(mid + (1 - mid) * 0.5, "#c2714f"),
      c(1, "#6b1c1c")
    ),
    colorbar = list(title = "RR")
  ) |>
  add_surface(
    x = x,
    y = y,
    z = matrix(1, nrow = length(y), ncol = length(x)),
    colorscale = list(c(0, "black"), c(1, "black")),
    opacity = 0.4,
    showscale = FALSE
  ) |>
  layout(
    title = "Exposure-Lag-Response Surface",
    scene = list(
      xaxis = list(title = "Temperature (°C)"),
      yaxis = list(title = "Lag", tickvals = y, ticktext = gsub("lag", "", y)),
      zaxis = list(title = "RR"),
      camera = list(eye = list(x = 1.5, y = -1.8, z = 0.8))
    )
  )
Click the image to explore the interactive Plotly version

The surface reveals two distinct risk regions. Hot temperatures produce a sharp, acute risk concentrated at the first lags, peaking at lag 0 and dissipating rapidly after the first lags. Cold temperatures produce a more modest and gradual increase in the first lags that does not fully disappear at longer lags. Intermediate temperatures near the MMT sit close to the RR = 1 reference plane across all lags.

The differential lag structure observed for heat- and cold-related mortality is consistent with known physiological mechanisms. Heat-related mortality tends to occur rapidly after exposure due to acute physiological stress, whereas cold-related mortality develops more gradually through delayed cardiovascular and respiratory effects, leading to increasing risk over longer lag periods.

Lag-response curves

We can also visualizes slices of the previous surface. For example, the lag-response relationship for different temperature values:

matRRfit <- cbind(
  melt(cpred$matRRfit.summary[,, "0.5quant"], value.name = "RR"),
  RR_lci = melt(
    cpred$matRRfit.summary[,, "0.025quant"],
    value.name = "RR_lci"
  )$RR_lci,
  RR_uci = melt(
    cpred$matRRfit.summary[,, "0.975quant"],
    value.name = "RR_uci"
  )$RR_uci
) |>
  rename(temperature = Var1, lag = Var2) |>
  mutate(
    lag = as.numeric(gsub("lag", "", lag))
  )

temp_min <- min(matRRfit$temperature, na.rm = TRUE)
temp_max <- max(matRRfit$temperature, na.rm = TRUE)
mmt_pos <- (cen - temp_min) / (temp_max - temp_min)

temp_values <- c(
  seq(0, mmt_pos, length.out = 6),
  seq(mmt_pos, 1, length.out = 6)[-1]
)

temp_colours <- c(
  "#053061",
  "#2166ac",
  "#4393c3",
  "#92c5de",
  "#d1e5f0",
  "#f7f7f7",
  "#fddbc7",
  "#f4a582",
  "#d6604d",
  "#b2182b",
  "#67001f"
)

p <- ggplot() +
  geom_line(
    data = matRRfit,
    aes(x = lag, y = RR, group = temperature, color = temperature),
    alpha = 0.35,
    linewidth = 0.35
  ) +
  scale_color_gradientn(
    colours = temp_colours, # same 11-colour palette as before
    values = temp_values,
    limits = c(temp_min, temp_max),
    name = "Temperature"
  ) +
  ggnewscale::new_scale_color() +
  geom_hline(
    yintercept = 1,
    linetype = "dashed",
    color = "grey30",
    linewidth = 0.5
  ) +
  scale_x_continuous(breaks = cpred$lag_at) +
  scale_y_continuous(trans = "log10", breaks = pretty_breaks(6)) +
  labs(
    title = "Lag-response curves by temperature",
    x = "Lag (days)",
    y = "Relative Risk (RR)"
  ) +
  theme_minimal() +
  theme(legend.position = "top", panel.grid.minor.x = element_blank()) +
  transition_states(
    temperature,
    transition_length = 1,
    state_length = 0
  ) +
  shadow_mark(past = TRUE, future = FALSE, alpha = 0.6)

animate(p, nframes = 300, fps = 15, end_pause = 100)

Cold temperatures (blue) gradually increase in the initial lags and then decline gradually without fully disappearing in the longer lags. Hot temperatures (red) show a different pattern: a higher risk immediately after lag 0, which drops rapidly and largely dissipates after the first lags:



Exposure-responses curves

We can also plot the exposure-responses curves by lag and the overall cumulative curve across all the lag period:

allRRfit <- data.frame(
  temperature = as.numeric(rownames(cpred$allRRfit.summary)),
  lag = "overall",
  RR = cpred$allRRfit.summary[, "0.5quant"],
  RR_lci = cpred$allRRfit.summary[, "0.025quant"],
  RR_uci = cpred$allRRfit.summary[, "0.975quant"]
)

RRfit <- rbind(matRRfit, allRRfit)

# Split data
RRfit_lags <- RRfit |>
  filter(!lag %in% c("overall")) |>
  mutate(lag = as.numeric(lag))
RRfit_overall <- RRfit |>
  filter(lag %in% c("overall"))

temps <- cpred$exp_at
t_cold <- temps[which.min(abs(temps - quantile(temps, 0.01)))]
t_hot <- temps[which.min(abs(temps - quantile(temps, 0.99)))]

# --- Top plot ---
p_main <- ggplot() +
  # Background: all lags, fading from vivid (small) to pale (large)
  geom_line(
    data = RRfit_lags,
    aes(x = temperature, y = RR, group = lag, color = lag),
    linewidth = 0.8
  ) +
  scale_color_gradientn(
    colours = c(
      "black",
      "#2b1d2f",
      "#4a2f5e",
      "#6a4c93",
      "#8b6bb8",
      "#b39cdb",
      "#d8c9f1",
      "#f3eef5"
    ),
    values = scales::rescale(c(0, 0.5, 1, 2, 3, 4, 5, 10, 20))
  ) +
  new_scale_color() +
  new_scale_fill() +
  # Credible intervals
  geom_ribbon(
    data = RRfit_overall,
    aes(
      x = temperature,
      ymin = RR_lci,
      ymax = RR_uci,
      fill = "1"
    ),
    alpha = 0.2
  ) +
  # Highlighted curves
  geom_line(
    data = RRfit_overall,
    aes(x = temperature, y = RR, color = "1"),
    linewidth = 1.2
  ) +
  geom_hline(
    yintercept = 1,
    linetype = "dashed"
  ) +
  scale_color_manual(values = "#a6761d", labels = "Overall (CrI95%)") +
  scale_fill_manual(values = "#a6761d", labels = "Overall (CrI95%)") +
  scale_y_continuous(
    transform = "log10",
    breaks = sort(c(0.8, pretty_breaks(5)(c(0.8, 4))))
  ) +
  labs(
    x = NULL,
    y = "Relative Risk (RR)",
    color = NULL,
    fill = NULL
  ) +
  theme_minimal() +
  theme(
    legend.position = "top",
    axis.text.x = element_blank(),
    plot.margin = margin(8, 8, 0, 8)
  )

# --- Bottom plot: histogram with annotated percentile lines ---
# center the color palette to the MMT temperature
temp_min <- min(cpred$exp_at, na.rm = TRUE)
temp_max <- max(cpred$exp_at, na.rm = TRUE)
mmt_pos <- (cen - temp_min) / (temp_max - temp_min)

temp_values <- c(
  seq(0, mmt_pos, length.out = 6),
  seq(mmt_pos, 1, length.out = 6)[-1]
)

temp_colours <- c(
  "#053061",
  "#2166ac",
  "#4393c3",
  "#92c5de",
  "#d1e5f0",
  "#f7f7f7",
  "#fddbc7",
  "#f4a582",
  "#d6604d",
  "#b2182b",
  "#67001f"
)

p_hist <- ggplot(london, aes(x = tmean)) +
  geom_histogram(
    aes(y = after_stat(density), fill = after_stat(x)),
    binwidth = 0.5,
    color = "black",
    linewidth = 0.2
  ) +
  geom_vline(
    xintercept = t_cold,
    linetype = "dashed",
    color = "#053061",
    linewidth = 0.6
  ) +
  geom_vline(
    xintercept = t_hot,
    linetype = "dashed",
    color = "#67001f",
    linewidth = 0.6
  ) +
  geom_vline(
    xintercept = cen,
    linetype = "dashed",
    color = "grey20",
    linewidth = 0.6
  ) +
  annotate(
    "text",
    x = t_cold,
    y = Inf,
    label = "1st pct",
    vjust = 1.4,
    hjust = 1.1,
    size = 3.2,
    color = "#053061"
  ) +
  annotate(
    "text",
    x = t_hot,
    y = Inf,
    label = "99th pct",
    vjust = 1.4,
    hjust = -0.1,
    size = 3.2,
    color = "#67001f"
  ) +
  annotate(
    "text",
    x = cen,
    y = Inf,
    label = "MMT",
    vjust = 1.4,
    hjust = -0.1,
    size = 3.2,
    color = "grey20"
  ) +
  scale_x_continuous(limits = c(temp_min, temp_max)) +
  scale_fill_gradientn(
    colours = temp_colours,
    values = temp_values,
    limits = c(temp_min, temp_max),
    name = "Temperature"
  ) +
  labs(x = "Temperature (°C)", y = "Density") +
  theme_minimal() +
  theme(
    plot.margin = margin(20, 8, 8, 8),
    legend.position = "bottom"
  )

# --- Combine ---
p_main / p_hist + plot_layout(heights = c(3, 1))


The overall cumulative curve (mustard) is clearly asymmetric: risk increases on both sides of the MMT, but the rise is much steeper for hot temperatures than for cold temperatures. The lag-0 curve (black), which reflects the immediate effect, behaves differently for cold than heat: it is below 1 at cold temperatures (reflecting the delayed nature of cold temperature effects) and increases approximately linearly for heat. The histogram confirms that most London days fall between 5°C and 20°C, so extreme temperatures, despite their high individual risks, are relatively rare events.

Attributable risk

We can also calculate attributable numbers and fractions from a B-DLNM, which allows to quantify the impact of all the observed exposures in 75+ years mortality. We compute the number of mortality events attributable to the temperature exposures (attributable number) and the fraction of all the mortality events it constitutes (attributable fraction).

Two different perspectives can be used:

    • Backward (dir = "back"): what today’s deaths were explained by past temperature exposures?

    • Forward (dir = "forw"): what future deaths will today’s temperature exposure cause?

Let’s use the forward perspective, more commonly used:

tictoc::tic()
attr_forw <- attributable(
  mod,
  london,
  name_date = "date",
  name_exposure = "tmean",
  name_cases = "mort_75plus",
  cen = cen,
  dir = "forw"
)
tictoc::toc()
110.12 sec elapsed

Attributable fraction evolution

We can plot the time series of daily attributable fractions (AF):

col_af <- "black"

temp_colours <- c(
  "#053061",
  "#2166ac",
  "#4393c3",
  "#92c5de",
  "#d1e5f0",
  "#f7f7f7",
  "#fddbc7",
  "#f4a582",
  "#d6604d",
  "#b2182b",
  "#67001f"
)

# Compute the position of MMT within the actual temperature range
temp_min <- min(london$tmean, na.rm = TRUE)
temp_max <- max(london$tmean, na.rm = TRUE)
mmt_pos <- (cen - temp_min) / (temp_max - temp_min)

temp_values <- c(
  seq(0, mmt_pos, length.out = 6),
  seq(mmt_pos, 1, length.out = 6)[-1]
)

af_med <- attr_forw$af.summary[, "0.5quant"]

af_min <- min(af_med, na.rm = TRUE) - 0.01
af_max <- max(af_med, na.rm = TRUE) + 0.01

df <- data.frame(
  date = london$date,
  x = yday(london$date),
  year = year(london$date),
  tmean = london$tmean,
  af = af_med
)

ggplot(df, aes(x = x)) +
  geom_rect(
    aes(
      xmin = x - 0.5,
      xmax = x + 0.5,
      ymin = af_min,
      ymax = af_max,
      fill = tmean
    )
  ) +
  scale_fill_gradientn(
    colours = temp_colours,
    values = temp_values,
    limits = c(temp_min, temp_max),
    name = "Temperature (°C)"
  ) +
  geom_line(
    aes(y = af),
    color = col_af,
    linewidth = 0.7
  ) +
  scale_y_continuous(
    name = "Attributable Fraction (AF)",
    breaks = seq(0, 1, by = 0.1),
    limits = c(af_min, af_max),
    expand = c(0, 0)
  ) +
  scale_x_continuous(
    breaks = yday(as.Date(paste0(
      "2000-",
      c("01", "03", "05", "07", "09", "11"),
      "-01"
    ))),
    labels = c("Jan", "Mar", "May", "Jul", "Sep", "Nov"),
    expand = c(0, 0)
  ) +
  facet_wrap(~year, ncol = 3, axes = "all_x") +
  labs(x = NULL) +
  theme_minimal(base_size = 11) +
  theme(
    panel.spacing.x = unit(0, "pt"),
    strip.text = element_text(face = "bold", size = 10),
    legend.position = "top",
    legend.key.width = unit(2.5, "cm")
  )



Sharp spikes in AF exceeding 60% are visible in summer 2003 and 2006, coinciding with the major European heatwaves. In general, summer episodes produce higher and more abrupt peaks in AF, whereas cold winter days are associated with more sustained elevations over time, though less pronounced in magnitude.

Total attributable burden

Summing across the full study period, the table quantifies the total mortality burden attributable to non-optimal temperature exposures in the 75+ population:

rbind(
  "Attributable fraction" = attr_forw$aftotal.summary,
  "Attributable number" = attr_forw$antotal.summary
) |>
  as.data.frame() |>
  round(3) |>
  gt(rownames_to_stub = TRUE)
mean sd 0.025quant 0.5quant 0.975quant mode
Attributable fraction 0.174 0.018 0.139 0.175 0.207 0.176
Attributable number 68857.597 7131.526 55071.066 69178.391 81995.459 69842.155
Over the full 2000-2012 period, approximately 17.5% (95% CrI: 13.9%-20.7%) of all deaths in the London 75+ population were attributable to non-optimal temperatures, corresponding to roughly 69,178 deaths (95% CrI: 55,071-81,996).

Conclusions

The {bdlnm} package provides a powerful and accessible implementation of Bayesian Distributed Lag Non-Linear Models in R. By combining the flexibility of DLNMs with full Bayesian inference via INLA, it enables researchers to better quantify uncertainty and fit complex exposure-lag-response relationships. This makes it a valuable tool for studying the health impacts of climate change and other environmental risks in increasingly data-rich settings.

This framework is not limited to environmental epidemiology. In fact it can be applied to any setting involving time-varying exposures and delayed effects (e.g., market shocks may affect asset prices over several days), making it a powerful and general tool for time series analysis.

Development is ongoing. Upcoming features include:

    • Multi-location analyses: pooling exposure-lag-response curves across different cities or regions within a single model
    • Spatial B-DLNMs (SB-DLNM): explicitly modelling spatial heterogeneity in the exposure-lag-response curves of different regions

The package is on CRAN. Bug reports and contributions are welcome via GitHub.

References

    • Gasparrini A. (2011). Distributed lag linear and non-linear models in R: the package dlnm. Journal of Statistical Software, 43(8), 1-20. doi:10.18637/jss.v043.i08.

    • Quijal-Zamorano M., Martinez-Beneito M.A., Ballester J., Marí-Dell’Olmo M. (2024). Spatial Bayesian distributed lag non-linear models (SB-DLNM) for small-area exposure-lag-response epidemiological modelling. International Journal of Epidemiology, 53(3), dyae061. doi:10.1093/ije/dyae061.

    • Rue H., Martino S., Chopin N. (2009). Approximate Bayesian inference for latent Gaussian models by using integrated nested Laplace approximations. Journal of the Royal Statistical Society: Series B, 71(2), 319-392. doi:10.1111/j.1467-9868.2008.00700.x.

    • Gasparrini A., Leone M. (2014). Attributable risk from distributed lag models. BMC Medical Research Methodology, 14, 55. doi:10.1186/1471-2288-14-55.

Agentic coding with R workshop

Join our workshop on Agentic coding with R,  which is a part of our workshops for Ukraine series! 


Here’s some more info: 


Title: Agentic coding with R 

Date: Thursday, April 2nd, 14:00 – 16:00 CET (Rome, Berlin, Paris timezone) 

Speaker: Charles Crabtree is a political scientist and Senior Lecturer in the School of Social Sciences at Monash University. His research sits at the intersection of political behavior, discrimination, and research methods, with work spanning experiments, text analysis, and large-scale observational data.

Description: This workshop introduces agentic coding for R: using AI assistants that can help you plan, write, run, and revise multi-step analysis workflows while keeping your work transparent and reproducible. Using Warp.dev as a concrete interface, we will walk through practical patterns for (1) turning messy research tasks into clear, checkable steps, (2) writing R code safely, (3) generating documentation and analysis notes as you work, and (4) developing a paper trail you can share with coauthors or future you. A key focus is adversarial agentic coding: pairing a “builder” agent with a separate “reviewer” agent that tries to break, audit, and improve the code the first agent produced—stress-testing assumptions, spotting silent failures, and proposing fixes. The emphasis is not on prompt tricks, but on reliable habits: how to constrain the agent, verify outputs, and integrate agentic help into real projects (data cleaning, modeling, tables and figures, and report generation). Participants will leave with copy-paste templates they can reuse immediately.

Minimal registration fee: 20 euro (or 20 USD or 800 UAH)






Please note that the registration confirmation is sent 1 day before the workshop to all registered participants rather than immediately after registration


How can I register?



  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the registration form, attaching a screenshot of a donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after donation).

If you are not personally interested in attending, you can also contribute by sponsoring a participation of a student, who will then be able to participate for free. If you choose to sponsor a student, all proceeds will also go directly to organisations working in Ukraine. You can either sponsor a particular student or you can leave it up to us so that we can allocate the sponsored place to students who have signed up for the waiting list.


How can I sponsor a student?


  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the sponsorship form, attaching the screenshot of the donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after the donation). You can indicate whether you want to sponsor a particular student or we can allocate this spot ourselves to the students from the waiting list. You can also indicate whether you prefer us to prioritize students from developing countries when assigning place(s) that you sponsored.


If you are a university student and cannot afford the registration fee, you can also sign up for the waiting list here. (Note that you are not guaranteed to participate by signing up for the waiting list).



You can also find more information about this workshop series,  a schedule of our future workshops as well as a list of our past workshops which you can get the recordings & materials here.


Looking forward to seeing you during the workshop!










 











Oops, Git! How to recover from common mistakes workshop

Join our workshop on  Oops, Git! How to recover from common mistakes, which is a part of our workshops for Ukraine series! 


Here’s some more info: 


Title: Oops, Git! How to recover from common mistakes

Date: Thursday, March 19th, 14:00 – 16:00 CET (Rome, Berlin, Paris timezone) 

Speaker: Maëlle Salmon, with a PhD in statistics, is a Research Software Engineer and blogger. At rOpenSci, she maintains the guide rOpenSci Packages: Development, Maintenance, and Peer Review, and has developed the babeldown and babelquarto packages for multilingual documents. At cynkra, she contributes to igraph and other packages. Maëlle is also the co-author of the book HTTP testing in R with Scott Chamberlain and created the R-hub blog. Additionally, she regularly contracts with various organizations, including research institutions, for R package development.

Description: Git is very useful. But it can also be scary when we make a mistake. Fortunately, with experience we make fewer mistakes… and/or we know better how to recover from them! In this hands-on workshop, we will learn how to get out of seven possible bad situations that can occur, with seven exercises inspired by the “Oh Shit, Git!” website https://ohshitgit.com/ and provided by the R package {saperlipopette}. https://docs.ropensci.org/saperlipopette/index.html By the end of this workshop, we’ll feel more capable of dealing with Git and our own fallibility.

This workshop is aimed at people who already have some experience using version control, although you don’t need to be an expert. It’s ideal for those who want to strengthen their confidence and autonomy in difficult situations.

Minimal registration fee: 20 euro (or 20 USD or 800 UAH)





Please note that the registration confirmation is sent 1 day before the workshop to all registered participants rather than immediately after registration


How can I register?



  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the registration form, attaching a screenshot of a donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after donation).

If you are not personally interested in attending, you can also contribute by sponsoring a participation of a student, who will then be able to participate for free. If you choose to sponsor a student, all proceeds will also go directly to organisations working in Ukraine. You can either sponsor a particular student or you can leave it up to us so that we can allocate the sponsored place to students who have signed up for the waiting list.


How can I sponsor a student?


  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the sponsorship form, attaching the screenshot of the donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after the donation). You can indicate whether you want to sponsor a particular student or we can allocate this spot ourselves to the students from the waiting list. You can also indicate whether you prefer us to prioritize students from developing countries when assigning place(s) that you sponsored.


If you are a university student and cannot afford the registration fee, you can also sign up for the waiting list here. (Note that you are not guaranteed to participate by signing up for the waiting list).



You can also find more information about this workshop series,  a schedule of our future workshops as well as a list of our past workshops which you can get the recordings & materials here.


Looking forward to seeing you during the workshop!










 









A practical introduction to multiple imputation of missing data with the R-package mice workshop

Join our workshop on A practical introduction to multiple imputation of missing data with the R-package mice, which is a part of our workshops for Ukraine series! 


Here’s some more info: 


Title: A practical introduction to multiple imputation of missing data with the R-package mice

Date: Thursday, January 22nd, 18:00 – 20:00 CET (Rome, Berlin, Paris timezone) 

Speaker: Thom Volker is a PhD candidate in Methodology and Statistics at Utrecht University. His research focuses on missing data, data privacy, and Bayesian statistics. He has contributed to the R package mice and is the author of the densityratio package.

Description: Missing data poses a challenge in many applied research settings. Although ad hoc methods such as listwise deletion or mean imputation are easy to implement, they can introduce substantial bias and lead to inefficient or invalid inferences. Multiple imputation offers a principled alternative that often improves statistical power and reduces bias.


In this session, you will learn when multiple imputation is appropriate and how to implement it effectively. We will cover essential topics such as specifying imputation models, evaluating the quality of imputations, and analyzing multiply imputed data in both inferential and predictive settings. By the end of the session, you will be equipped to address common missing-data problems using the mice package in R.

Minimal registration fee: 20 euro (or 20 USD or 800 UAH)





Please note that the registration confirmation is sent 1 day before the workshop to all registered participants rather than immediately after registration


How can I register?



  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the registration form, attaching a screenshot of a donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after donation).

If you are not personally interested in attending, you can also contribute by sponsoring a participation of a student, who will then be able to participate for free. If you choose to sponsor a student, all proceeds will also go directly to organisations working in Ukraine. You can either sponsor a particular student or you can leave it up to us so that we can allocate the sponsored place to students who have signed up for the waiting list.


How can I sponsor a student?


  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the sponsorship form, attaching the screenshot of the donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after the donation). You can indicate whether you want to sponsor a particular student or we can allocate this spot ourselves to the students from the waiting list. You can also indicate whether you prefer us to prioritize students from developing countries when assigning place(s) that you sponsored.


If you are a university student and cannot afford the registration fee, you can also sign up for the waiting list here. (Note that you are not guaranteed to participate by signing up for the waiting list).



You can also find more information about this workshop series,  a schedule of our future workshops as well as a list of our past workshops which you can get the recordings & materials here.


Looking forward to seeing you during the workshop!










 











R Package Development in Positron workshop

Join our workshop on R Package Development in Positron, which is a part of our workshops for Ukraine series! 


Here’s some more info: 


Title: R Package Development in Positron

Date: Thursday, January 15th, 18:00 – 20:00 CET (Rome, Berlin, Paris timezone) 

Speaker: Stephen D. Turner is an associate professor of data science at the University of Virginia School of Data Science. Prior to re-joining UVA he was a data scientist in national security and defense consulting, and later at a biotech company (Colossal – the de-extinction company) where he built and deployed scores of R packages.

Description: This workshop will cover R package development in Positron using devtools, usethis, testthat, covr, and a few AI tools along the way to help.

Minimal registration fee: 20 euro (or 20 USD or 800 UAH)




Please note that the registration confirmation is sent 1 day before the workshop to all registered participants rather than immediately after registration


How can I register?



  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the registration form, attaching a screenshot of a donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after donation).

If you are not personally interested in attending, you can also contribute by sponsoring a participation of a student, who will then be able to participate for free. If you choose to sponsor a student, all proceeds will also go directly to organisations working in Ukraine. You can either sponsor a particular student or you can leave it up to us so that we can allocate the sponsored place to students who have signed up for the waiting list.


How can I sponsor a student?


  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the sponsorship form, attaching the screenshot of the donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after the donation). You can indicate whether you want to sponsor a particular student or we can allocate this spot ourselves to the students from the waiting list. You can also indicate whether you prefer us to prioritize students from developing countries when assigning place(s) that you sponsored.


If you are a university student and cannot afford the registration fee, you can also sign up for the waiting list here. (Note that you are not guaranteed to participate by signing up for the waiting list).



You can also find more information about this workshop series,  a schedule of our future workshops as well as a list of our past workshops which you can get the recordings & materials here.


Looking forward to seeing you during the workshop!










 









Inference for non-probability samples with nonprobsvy package in R workshop

Join our workshop on Inference for non-probability samples with nonprobsvy package in R, which is a part of our workshops for Ukraine series! 


Here’s some more info: 


Title: Inference for non-probability samples with nonprobsvy package in R

Date: Thursday, January 8th, 18:00 – 20:00 CET (Rome, Berlin, Paris timezone) 

Speaker: Maciej Beręsewicz, an R enthusiast, co-organiser of several R conferences including European R Users Meeting 2016, and an R developer. Currently employed as an assistant professor in the Department of Statistics, Poznan University of Economics and Business, and the head of the Centre for the Methodology of Population Studies at the Statistical Office in Poznan. Main research topics: non-probability samples, administrative data, and population size estimation.

Description: During the workshop, the following topics will be covered: 1) basic theory of inference for non-probability samples, 2) how to use population-level information as well as probability samples to correct selection bias using various ways (inverse probability weighting, mass imputation, and double robust approach), 3) how to use the nonprobsvy package to estimate population mean through case studies, 4) how to estimate uncertainty and report results in R. For more details, please see the following working paper: Chrostowski, Chlebicki & Beręsewicz, (2025). nonprobsvy–An R package for modern methods for non-probability surveys. arXiv preprint arXiv:2504.04255 (accepted to the Journal of Statistical Software).

Minimal registration fee: 20 euro (or 20 USD or 800 UAH)




Please note that the registration confirmation is sent 1 day before the workshop to all registered participants rather than immediately after registration


How can I register?



  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the registration form, attaching a screenshot of a donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after donation).

If you are not personally interested in attending, you can also contribute by sponsoring a participation of a student, who will then be able to participate for free. If you choose to sponsor a student, all proceeds will also go directly to organisations working in Ukraine. You can either sponsor a particular student or you can leave it up to us so that we can allocate the sponsored place to students who have signed up for the waiting list.


How can I sponsor a student?


  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the sponsorship form, attaching the screenshot of the donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after the donation). You can indicate whether you want to sponsor a particular student or we can allocate this spot ourselves to the students from the waiting list. You can also indicate whether you prefer us to prioritize students from developing countries when assigning place(s) that you sponsored.


If you are a university student and cannot afford the registration fee, you can also sign up for the waiting list here. (Note that you are not guaranteed to participate by signing up for the waiting list).



You can also find more information about this workshop series,  a schedule of our future workshops as well as a list of our past workshops which you can get the recordings & materials here.


Looking forward to seeing you during the workshop!










 











{talib}: Candlestick Pattern Recognition in R

{talib} is a new R-package for Technical Analysis (TA) and Candlestick Pattern Recognition (Yeah, the patterns traders bet their lifesavings on….). In this post I will show basic example on how {talib} works, and how it compares performance-wise with {TTR}.

Basic example

In this example I will identify all ‘Harami’ patterns, and calculate the Bollinger Bands of the SPDR S&P 500 ETF (SPY).

Identify Harami patterns

x <- talib::harami(
  talib::SPY
)

talib::harami() is a S3 function and returns a matrix of the same length of the input. The number of identified patterns can counted as non-zero entires.

cat(
  "identified patterns:",
  sum(x[, 1] != 0, na.rm = TRUE)
)
#> identified patterns: 35

The Harami pattern can be bullish (1) or bearish (-1) and counted the same way

cat(
  "identified bullish patterns:",
  sum(x[, 1] == 1, na.rm = TRUE)
)
#> identified bullish patterns: 20

cat(
  "identified bearish patterns:",
  sum(x[, 1] == -1, na.rm = TRUE)
)
#> identified bearish patterns: 15

Charting

The Harami pattern can be plotted using talib::chart() with talib::bollinger_bands() to add Bollinger Bands to the chart.

{
  talib::chart(talib::SPY)
  talib::indicator(talib::harami)
  talib::indicator(talib::bollinger_bands)
}

Benchmarks

An often asked question about {talib} in relation to {TTR}, is what it “brings to the table”. Other than Candlestick Patterns and interactive charts, it brings speed and efficiency.

To demonstrate the difference in speed, I will create a univariate price series with 1 million entries.

set.seed(1903)
x <- runif(n = 1e6, min = 100, max = 150)

The univariate series x will be passed into the Bollinger Bands from each package:

bench::mark(
  talib::bollinger_bands(x),
  TTR::BBands(x),
  min_iterations = 10,
  check = FALSE
)[, c(1, 2, 3, 5)]
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
#> # A tibble: 2 × 4
#>   expression                     min   median mem_alloc
#>   <bch:expr>                <bch:tm> <bch:tm> <bch:byt>
#> 1 talib::bollinger_bands(x)   6.65ms   9.07ms    22.9MB
#> 2 TTR::BBands(x)             65.12ms  72.42ms   139.3MB

In this benchmark {talib} is faster, and more memory efficient, than {TTR}.

{talib} is still under development, and will most likely not be submitted to CRAN before next year. Until then it can be installed from Github: pak::pak("serkor1/ta-lib-R")

Feel free to stop by the repository here: https://github.com/serkor1/ta-lib-R.

Created on 2025-11-16 with reprex v2.1.1

Free AI and Data Courses with 365 Data Science—Unlimited Access until Nov. 21

365 Data Science opens its doors this November 100% for free!

From November 6 to November 21, 2025 (8 a.m. UTC), enjoy unlimited access to the entire platform, including a wide range of R programming courses and projects.

This offer features expert-led courses, practical projects, and interactive exercises covering diverse topics in AI and data science. With flexible 24/7 access, learners can study at their own pace—making it an ideal opportunity to grow and advance in these fields.

The Initiative’s Fifth and Biggest Year

Now in its fifth year, 365 Data Science reaffirms its dedication to delivering accessible, high-quality education through its annual Free Access Initiative, originally launched in 2020 during the global pandemic. CEO Ned Krastev emphasizes the accelerating demand for AI-driven skills, stating, “The AI and data landscape is evolving faster than ever, creating extraordinary opportunities for career growth for those ready to embrace new technologies.”

The initiative’s reach has grown remarkably—2024 marked its most successful edition yet, with over 200,000 unique learners from 215 countries participating, accumulating 6.9 million minutes of learning and earning more than 35,000 certificates.

Krastev adds, “Artificial intelligence is reshaping industries at an unprecedented pace. Understanding how AI systems function—and how to build, deploy, and integrate them—has become vital for anyone pursuing a data-driven career. At 365 Data Science, our mission is to close that gap by equipping learners not only with data literacy but also with hands-on expertise in AI engineering and intelligent agents—the skills that will define the next generation of tech professionals.”

365 Data Science empowers learners to move beyond traditional analytics and step confidently into the era of AI engineering and intelligent agents. The platform equips users with the skills to design, deploy, and collaborate with AI systems capable of reasoning, planning, and acting autonomously.

What’s Included

During this special period, learners gain unlimited access to the entire 365 Data Science platform—a comprehensive destination for mastering data and AI. The platform offers over 115 expert-led courses, covering everything from fundamental data skills to advanced topics in machine learning, AI, and AI engineering.

Participants can apply their knowledge through real-world projects that mirror practical work scenarios, gaining hands-on experience in solving meaningful problems. Newly added interactive exercises and guided challenges deepen understanding and strengthen key concepts.

To support career growth, 365 Data Science also provides structured, career-focused learning paths—guiding learners step by step from beginner to job-ready professional with a clear roadmap to success in today’s AI-driven world.

Certifications that Open Doors

In today’s fast-changing job market, recognized certifications are essential to stand out. Through this free access initiative, 365 Data Science offers learners the chance to earn industry-recognized certificates at no cost.

These credentials validate practical expertise in data analytics, AI, and machine learning, enhancing employability and building credibility with employers worldwide. By offering verifiable, career-boosting certifications, the initiative bridges the gap between learning and professional achievement—showcasing real-world competence.

R Programming: A Core Focus

365 Data Science acknowledges the central role of R in modern data analysis and visualization, reflected in its carefully curated course selection:

  • Introduction to R Programming: A beginner-friendly course that builds a strong foundation in R syntax, data structures, and essential operations—perfect for kick-starting your R journey.
  • The Complete Data Visualization Course with Python, R, Tableau, and Excel: A comprehensive program that highlights R’s powerful visualization tools while exploring multiple platforms for data storytelling.
  • Data-Driven Business Growth: Learn how to apply R to real-world business cases, adopting a growth mindset to generate meaningful insights and drive measurable impact.

Additionally, learners can explore a variety of complementary courses designed to deepen their analytical toolkit, covering Statistics, Probability, Statistical Tests in Sales and Marketing, Data Preprocessing, and more—helping you master both R and other key programming languages in data science.

Don’t Miss this Opportunity

Free access runs from November 6 to November 21, 2025.

In a world increasingly powered by data and artificial intelligence, staying ahead of the curve is essential. This three-week open-access period from 365 Data Science offers a rare opportunity to invest in your future—whether you’re just getting started, switching careers, or advancing your expertise in AI and data.

Don’t miss your chance to build in-demand skills, earn industry-recognized certificates, and take the next step toward a successful career in data science and AI engineering.

The future belongs to those who prepare for it today—start your journey for free with 365 Data Science.

A Gentle Introduction to Mathematical Simulation in R workshop

Join our workshop on A Gentle Introduction to Mathematical Simulation in R, which is a part of our workshops for Ukraine series! 


Here’s some more info: 


Title:A Gentle Introduction to Mathematical Simulation in R

Date: Thursday, December 11th, 18:00 – 20:00 CET (Rome, Berlin, Paris timezone) 

Speaker: Damie Pak has a PhD in Biology from The Pennsylvania State University, and her research has been in theoretical ecology with a fondness for agricultural insects and infectious disease. She uses R for everything—from statistical analyses to mathematical modeling.

Description: While R is best known as a tool for statistical analyses, it can also be used for mathematical simulations. Specifically, we can use compartmental modeling—a mathematical framework for capturing individuals transitioning between different stages—to investigate dynamics found in nature. Notably, we will use the package {deSolve} to model the Lotka-Volterra predator-prey model to showcase the diversity of dynamics that can be observed from a remarkably simple system of equations. The workshop will cover the basic mathematics, the structure of writing a differential equation model in R, as well as ways to visualize the output for better insight using {ggplot} and {gganimate}.

Minimal registration fee: 20 euro (or 20 USD or 800 UAH)




Please note that the registration confirmation is sent 1 day before the workshop to all registered participants rather than immediately after registration


How can I register?



  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the registration form, attaching a screenshot of a donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after donation).

If you are not personally interested in attending, you can also contribute by sponsoring a participation of a student, who will then be able to participate for free. If you choose to sponsor a student, all proceeds will also go directly to organisations working in Ukraine. You can either sponsor a particular student or you can leave it up to us so that we can allocate the sponsored place to students who have signed up for the waiting list.


How can I sponsor a student?


  • Save your donation receipt (after the donation is processed, there is an option to enter your email address on the website to which the donation receipt is sent)

  • Fill in the sponsorship form, attaching the screenshot of the donation receipt (please attach the screenshot of the donation receipt that was emailed to you rather than the page you see after the donation). You can indicate whether you want to sponsor a particular student or we can allocate this spot ourselves to the students from the waiting list. You can also indicate whether you prefer us to prioritize students from developing countries when assigning place(s) that you sponsored.


If you are a university student and cannot afford the registration fee, you can also sign up for the waiting list here. (Note that you are not guaranteed to participate by signing up for the waiting list).



You can also find more information about this workshop series,  a schedule of our future workshops as well as a list of our past workshops which you can get the recordings & materials here.


Looking forward to seeing you during the workshop!