EARL 2026 Brighton - A Postcredit Blog

EARL/26
Author

Frank Liang

Published

October 3, 2026

Thank you EARL 2026

8th October, I gave a light-hearted lighting talk titled “let’s hunt alien” EARL 2026, in Brighton! For those of you who was with us and enjoyed by talk, a big thank you! I’m overwhelmed by your reactions. And thank you to Data Cove team, specfically Laura who has given me so much encouragement.

The talk is based on my blog back in Jun 2024, so most of the code will be there!

Side note, I wanted this talk to be a almost a scifi movie watching experience so I styled the last slide like a movie credit scene.

Additional Code

Most code will be in my original code blog back in Jun 2024. The only other code missing here is clustering and wrangling.

Load relevant datas
library(dplyr)
library(dbplyr)
library(ggplot2)
library(duckdb)
## So the only way is to laod that data from geometry
conn <- DBI::dbConnect(duckdb::duckdb("ufo.db",read_only = TRUE))

ufo_data = conn |> 
    tbl("analysis.ufo_observe_by_state_year")

us_counties = conn |> 
    tbl(sql("select * exclude geometry, st_asWkB(geometry) as geometry from clean.us_state_polygon"))
us_count_full_name_ = conn |> 
    tbl(sql("from clean.us_population")) |> 
    group_by(county_id) |> 
    slice_max(n=1,order_by=api_year) |> 
    select(county_id,fullname=GEONAME) |> 
    collect() |> 
    # this is to make the name in a printable format for visualise on specific state
    mutate(
        full_name=stringr::str_extract(fullname, "[\\w\\s]+,[\\w\\s]+") |> 
            stringr::str_replace(",\\s","\n")
    ) |> 
    collect()
mart = ufo_data |> 
    left_join(us_counties, "county_id") |> 
    mutate(year = as.integer(year))
class(mart) <- c("ufo_mart",class(mart))

My favourite way of doing this in R is to create a S3 class for different dataframe.

Functions To Help with Aggregation & Graphics
agg.ufo_mart = function(df) {
    df |> 
    summarise(
        ufo_count = sum(ufo_count),
        ufo_multi_observe = sum(ufo_multi_observe),
        geometry = first(geometry),
        density = mean(as.numeric(DENSITY), na.rm=TRUE),
        population = mean(as.numeric(POP))
    ) |> 
    mutate(
        ufo_count_density = (ufo_count / density),
        ufo_multiserve_density = (ufo_multi_observe / density)
    )
}
collect_sf.ufo_mart =function(df) {
    x = df |> 
        collect() |> 
        mutate(
            geometry=sf::st_as_sfc(geometry, crs=4269)
        ) |> 
        sf::st_as_sf()
    class(x) <- c("ufo_mart", class(x))
    return(x)
}
vis.ufo_mart = function(df, variable, ..., title) {
    df |> 
    tmap::tm_shape() + 
    tmap::tm_polygons(
        fill = variable,
        fill.scale = tmap::tm_scale_continuous_pseudo_log(
            ...
            ),
        fill.legend = tmap::tm_legend(position=c("right","bottom"),title=title),
        
    ) + 
    tmap::tm_layout(legend.outside=TRUE)
}
as_ufo_mart=function(x) {
    if (inherits(x, "ufo_mart")) {
        return(x)
    } else {
        class(x) <- c("ufo_mart", class(c))
        return(x)
    }
}
vis = function(...) {UseMethod("vis")}
collect_sf = function(...) {UseMethod("collect_sf")}
agg = function(...) {UseMethod("agg")}
mutate.ufo_mart = function(...){
    as_ufo_mart(NextMethod("mutate"))
}
vis_top_score = function(...) {UseMethod("vis_top_score")}
vis_top_score.ufo_mart = function(x, 
    variable,top_n,desc=TRUE, 
    label_from = "full_name", 
    colorpalette = "brewer.bu_pu", 
    text_size=0.7,
    limits=c()
) {
    variable = enquo(variable)
    var_label = as_label(variable)
    if (length(limits) == 0) {
        limits = pull(x, {{variable}}) |> range()
    }
    x=x |> 
        arrange({{variable}} |> desc())
    if (desc) {
        x = x |> slice_head(n=top_n)
    } else {
        x = x |> slice_tail(n=top_n)
    }
    mid_point = pull(x, {{variable}}) |> median()
    label_glue = glue::glue("{{{label_from}}}")
    x |> 
        # transform
        mutate(
            "{var_label}" := round(!!variable,2),
            text_color=ifelse({{variable}} > mid_point, "white", "black"),
            label = glue::glue(label_glue)
        ) |> 
        sf::st_as_sf() |> 
        tmap::tm_shape() +
        tmap::tm_polygons(
            fill=var_label,
            fill.scale = tmap::tm_scale_continuous_log(values=colorpalette),
            fill.legend = tmap::tm_legend_hide(),
            fill_alpha=0.5,
            limits=limits
        ) +
        tmap::tm_text(
            text="label",
            col="text_color",
            size=text_size
        )
}
Function wraper for spdep local moran
# Local Moran Statistics -----------------------
local_moran_c = function(variable, nb, get = c("Ii","E.Ii","Var.Ii","Pr(z > E(Ii))")) {
    lw = spdep::nb2listw(nb, style="W",zero.policy = TRUE)
    t = spdep::localmoran_perm( {{variable}}, lw, alternative="greater")
    t[,get]
}
local_moran_c_quadr= function(variable, nb, get = c("Ii","E.Ii","Var.Ii","Pr(z > E(Ii))")) {
    lw = spdep::nb2listw(nb, style="W",zero.policy = TRUE)
    t = spdep::localmoran_perm( {{variable}}, lw, alternative="greater")
    attributes(t)$quadr$mean
}
# some interesting event data
dot_area_51 = sf::st_point(c(-115.80833,37.23333)) |> 
    sf::st_sfc(crs=4326)
dot_massachuesetts_bay = sf::st_point(c(-71.05,42.36)) |> 
    sf::st_sfc(crs=4326)

ufo_state_summary_since_xxxx = mart |> 
    filter(year > 1890) |> 
    group_by(county_id) |> 
    agg() |> 
    collect_sf()
nb=spdep::poly2nb(ufo_state_summary_since_xxxx, queen=TRUE)
ufo_state_summary_since_xxxx = ufo_state_summary_since_xxxx |> 
    mutate(
        ufo_density_moran_p = local_moran_c(ufo_count_density,nb, "Pr(z > E(Ii))"),
        ufo_density_moran_i = local_moran_c(ufo_count_density,nb,"Ii"),
        ufo_density_lisa = local_moran_c_quadr(ufo_count_density,nb)
    ) |> 
    left_join(tibble::as_tibble(us_count_full_name_) |>select(county_id, full_name) )

Local Moran’s I Map

ufo_state_summary_since_xxxx |> 
    ggplot() +
    geom_sf(
        aes(geometry=geometry, fill=ufo_density_lisa)
    ) +
    scale_fill_viridis_d(option = "F") +
    theme_minimal() +
    theme(
        plot.title = ggtext::element_markdown(),
        plot.subtitle = ggtext::element_markdown(),
        plot.caption = ggtext::element_markdown()
    ) +
    labs(
        title="<span style='font-weight: bold'>Alien have a thing for Cowboys</span>",
        subtitle="Spatial auto-correlation is stronger in <span style='color: #D35400 font-weight: bold'> Western states </span> than in the <span style='color: #7C0969'>Eastern states</span>",
        caption="Using local moran statistic"
    )

UFO Sighting Absorbed Population Density

conn |> DBI::dbExecute("load spatial;")
[1] 0
ufo_points = conn |> 
    tbl(sql("select * from clean.ufo_data_standardized")) |> 
    collect() |> 
    mutate(
        geometry = sf::st_as_sfc(geometry,crs='EPSG:4269')
    ) |> 
    sf::st_as_sf()
Observation Heatmap with annotation
tmap::tmap_mode("plot")
ℹ tmap modes "plot" - "view"
ℹ toggle with `tmap::ttm()`
Observation Heatmap with annotation
.area_51_label.col = "black"
.area_51_label.fill = "#F5B027"
.ufo_dot_color="#28B463"
.moran_color_fill = "magma"
## scatter plot mini map ---------------------
top_x_county = ufo_state_summary_since_xxxx |> 
    # left_join(us_count_full_name_, by="county_id") |> 
    arrange(ufo_density_moran_i |> desc()) |> 
    mutate(label = full_name) |> 
    head(2)
m_top_x_county_outline = top_x_county |> 
    sf::st_as_sf() |> 
    tmap::tm_shape() +
    tmap::tm_basemap() +
    tmap::tm_polygons() + 
    tmap::tm_text(
        text="label",
        col="white",
        size=0.5
    )
m_ufo_points = ufo_points |> 
    inner_join(select(tibble::as_tibble(top_x_county), county_id), by="county_id") |> 
    tmap::tm_shape() + 
    tmap::tm_symbols(
        shape = tmap::tmap_icons("asset/ufo.png"),
        size=0.3
    )
m_area_51=tmap::tm_shape(dot_area_51) + 
    tmap::tm_symbols(
        shape=tmap::tmap_icons("asset/Area-51-UFO-Sign.png"),
    )
## Another minimap for visualise first UFO sighting event
m_mass_bay = tmap::tm_shape(dot_massachuesetts_bay) + 
    tmap::tm_symbols(
        shape = tmap::tmap_icons("asset/alien-abduction.png")
    )
    
## big choro plot mini map ---------------------
# tmap::tmap_mode("plot")
ufo_state_summary_since_xxxx |> 
    tmap::tm_shape() + 
    tmap::tm_basemap() +
    tmap::tm_polygons(
        id = "county_id",
        fill = "ufo_count_density",
        fill.scale = tmap::tm_scale_continuous_pseudo_log(
            values=.moran_color_fill,
        ),
        fill.legend = tmap::tm_legend_hide()
    ) + 
    tmap::tm_layout(legend.outside=TRUE) + 
    m_area_51 +
    tmap::tm_text(
        text="51",
        col="white",
        size = 0.7
    ) +
    m_mass_bay 

UFO Sighting without Absorbed Population Density

Raw UFO count without anything
tmap::tmap_mode("plot")
ℹ tmap modes "plot" - "view"
Raw UFO count without anything
top_count = ufo_state_summary_since_xxxx |> 
    slice_max(ufo_count) |> 
    mutate(text = glue::glue("
    {round(ufo_count)} Alien Sighting 
    in {full_name}
    ")) |> 
    tmap::tm_shape() + 
    tmap::tm_polygons(fill="midnightblue") +
    tmap::tm_text(text="text",col="white")
ufo_state_summary_since_xxxx |> 
    tmap::tm_shape() + 
    tmap::tm_basemap() +
    tmap::tm_polygons(
        fill = "ufo_count",
        fill.scale = tmap::tm_scale_continuous_pseudo_log(
            values='cividis',
        ),
        fill.legend = tmap::tm_legend_hide()
    ) + 
    m_area_51 +
    m_mass_bay +
    # tmap::tm_inset(top_count,group_id = "1",height=20, width = 20, position = c("right","bottom")) +
    # tmap::tm_components(group_id = "1", position = c("bottom","left")) + 
    tmap::tm_layout(
        legend.outside=TRUE,
        outer.margins = rep(0,4),
        inner.margins = rep(0.09,4)
    ) + 
    tmap::tm_title("Raw UFO Event count")

Reference