Week 5: Interactive Visuals

R
Author

Robert W. Walker

Published

February 13, 2023

Meeting Date: February 13, 2023.

Last updated: 2023-04-10 13:53:03

Timezone: America/Los_Angeles

Data from Our World in data, example sankey

Class Plan

  1. AMA
  2. A Review of Nice Tables?
  3. Interactive Graphics in R
    • Network data and interaction with networkD3.
    • Plotly and ggplotly
    • Crosstalk related tools
    • ggiraph

Slides:

Week 5 Slides

The Site I am Building

My github main page

Readings:

  • Quarto Documentation [if not already done]
  • The gt docs link
  • The flextable book

Resources

Tables

Interactive Graphics

Homework

The fifth assignment consists of adding at two interactive graphics.

Syllabus Module for Week 5

Deliverables: an email containing the URLs for the interactive visualization posts.

Network Visualization

The Data

Code
library(tidyverse)
library(jsonlite)
URL <- paste0("https://cdn.rawgit.com/christophergandrud/networkD3/","master/JSONdata/energy.json")
Energy <- fromJSON(URL)
Links <- Energy$links 
pivot_wider(Links, id_cols=source, names_from = target, values_from = value) %>% knitr::kable()
source 1 2 3 4 5 9 12 13 14 16 17 18 19 20 21 22 24 26 15 28 30 31 32 33 37 42 41 11
0 124.729 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
1 NA 0.597 26.862 280.322 81.144 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
6 NA 35.000 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
7 NA NA NA 35.000 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
8 NA NA NA NA NA 11.606 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
10 NA NA NA NA NA 63.965 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
9 NA NA NA 75.571 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
11 NA NA NA NA NA NA 10.639 22.505 46.184 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
15 NA NA 56.691 NA NA NA 342.165 40.858 113.726 104.453 27.14 37.797 4.412 7.863 90.008 93.494 NA NA NA NA NA NA NA NA NA NA NA NA
23 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 40.719 NA NA NA NA NA NA NA NA NA NA NA
25 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 82.233 NA NA NA NA NA NA NA NA NA NA NA
5 NA NA 1.401 NA NA NA 48.580 0.129 NA NA NA NA 2.096 NA NA NA NA 151.891 NA NA NA NA NA NA NA NA NA NA
27 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 7.013 NA NA NA NA NA NA NA NA NA
17 NA NA 6.242 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 20.897 NA NA NA NA NA NA NA NA
28 NA NA NA NA NA NA NA NA NA NA NA 20.897 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
29 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 6.995 NA NA NA NA NA NA NA NA NA
2 NA NA NA NA NA NA 121.066 NA NA NA NA 135.835 3.640 4.413 NA NA NA NA NA NA 128.69 14.458 206.267 33.218 NA NA NA NA
34 4.375 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
24 NA NA NA NA 122.952 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
35 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 839.978 NA NA NA NA NA NA NA NA NA NA
36 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 504.287 NA NA NA
38 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 107.703 NA NA NA
37 NA 611.990 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
39 77.810 NA NA 56.587 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
40 NA NA NA NA NA NA NA 70.672 193.026 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
41 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 59.901 NA NA NA NA NA NA NA NA NA
42 NA NA NA NA NA NA NA NA 19.263 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
43 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 19.263 59.901 NA
4 NA NA NA NA NA NA 46.477 NA NA NA NA NA 0.882 NA NA NA NA 400.120 NA NA NA NA NA NA NA NA NA NA
26 NA NA 787.129 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 525.531 NA NA NA NA NA NA NA NA 79.329
44 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 9.452 NA NA NA NA NA NA NA NA NA
45 182.010 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
46 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 19.013 NA NA NA NA NA NA NA NA NA
47 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 289.366 NA NA NA NA NA NA NA NA NA

The Plot

Code
library(networkD3)
# Plot
sankeyNetwork(Links = Energy$links, 
              Nodes = Energy$nodes, 
              Source = "source",
              Target = "target", 
              Value = "value", 
              NodeID = "name",
              units = "TWh", 
              fontSize = 12, 
              nodeWidth = 30)

Stocks and Plotly

The plotly package

plotly as a package has far more it can do.

Code
library(plotly); library(magrittr)
library(tidyquant)
# Use tidyquant to get the data
INTC <- tq_get("INTC")
# Slice off the most recent 90 days
INTC.tail.90 <- tail(INTC, 90)
INTC.tail <- INTC.tail.90
# Create a counter of days
INTC.tail$ID <- seq.int(nrow(INTC.tail))
# Round the prices to 2 digits
INTC.tail %<>% mutate(close = round(close, digits=2))

Now we want a function to create the dataset for each stage of the animation. There are a few ways to do this but most involve writing a function to create them. This example function comes from the plotly documents.

Code
# This is in the example for plotly paths
# First a supporting function: getLevels takes input x
getLevels <- function (x) {
  # if x is a factor
    if (is.factor(x)) 
  # grab the levels of x
        levels(x)
  # if x is not a factor, sort unique values of x
    else sort(unique(x))
}
# Two inputs, the data and the variable to form the splits along the x-axis
accumulate_by <- function(dat, var) {
  # This handles linking variables to their environment
  var <- lazyeval::f_eval(var, dat)
  # get the levels of the given variable using the function above
  lvls <- getLevels(var)
  # use lapply, tidy would use map to iterate over the levels in `lvls` and column bind the data with frame denotes by lvls[[x]]
  dats <- lapply(seq_along(lvls), function(x) {
   cbind(dat[var %in% lvls[seq(1, x)], ], frame = lvls[[x]])
  })
  # bind the rows together
  dplyr::bind_rows(dats)
}
# Invoke the function on our ID variable
INTC.tail <- INTC.tail %>% accumulate_by(~ID)
# Create a figure of ID and close for each frame value using plotly's version of a line plot: type:scatter-mode:lines
# The rest is standard plotly
fig <- INTC.tail %>% plot_ly(
  x = ~ID, 
  y = ~close, 
  frame = ~frame,
  type = 'scatter', 
  mode = 'lines', 
  # This is short for fill to zero on the y-axis
  fill = 'tozeroy',
  fillcolor='rgba(73, 26, 201, 0.5)',
  line = list(color = 'rgb(73, 26, 201)'),
  text = ~paste("Date: ", date, "<br>Close: $", close), 
  hoverinfo = 'text'
)
# Add the layout; one title and two axes
# I also mess with the margin to keep the figure from being cut off.
fig <- fig %>% layout(
  title ="Intel Stock Closing Price: Last 90 Days",
  yaxis = list(
    title = "Close", 
    range = c(0,50), 
    zeroline = F,
    tickprefix = "$"
  ),
  xaxis = list(
    title = "Day", 
    range = c(0,90), 
    zeroline = F, 
    showgrid = F
  ),
  margin = list(t=120) # adjust the plot margin to avoid cutting off letters
) 
# Animate the figure with 100 frames
fig <- fig %>% animation_opts(
  frame = 100, # transition time 100 ms
  transition = 0, # duration of smooth transition in ms
  redraw = FALSE # redraw the plot at each transition?
)
fig <- fig %>% animation_slider(
  currentvalue = list(
    prefix = "Day "
  )
)
fig

An easier plotly for these data because they are OHLC

A link to a stand-alone document with all the data.

Code
# basic example of ohlc charts
# custom colors
i <- list(line = list(color = '#000000')) # black
d <- list(line = list(color = '#FF0000')) # red
# Create the figure
fig.2 <- INTC.tail.90 %>%
  plot_ly(x = ~date, type="ohlc",
          open = ~open, close = ~close,
          high = ~high, low = ~low,
          increasing = i, decreasing = d)
fig.2

Human Rights Data from Fariss

I will load the necessary libraries for this before loading the data.

Code
library(tidyverse)
library(magrittr)
library(DT)
load(url("https://github.com/robertwwalker/robertwwalker.github.io/raw/main/posts/HumanRightsTable/data/HumanRightsProtectionScores_v4.01.RData"))
HR.Data <- x
rm(x)
HR.Data.Selection <- HR.Data %>% 
  mutate(country_name = as.factor(country_name)) %>%
  select(country_name, theta_mean, theta_sd) %>%
  group_by(country_name) %>%
  summarise(Mean = mean(theta_mean),
            SD = mean(theta_sd)) %>% 
  mutate(Mean = round(Mean, digits=2),
         SD = round(SD, digits=2))

To link the data selection and the visualization, I need one line of code necessary to share the data.

Code
# Get bscols onto rows with widths
# https://stackoverflow.com/questions/72064781/in-package-crosstalk-are-there-have-function-bsrows-and-rowscols-mixed
library(crosstalk); library(plotly)
shared <- SharedData$new(HR.Data.Selection)
bscols(widths = c(12, 12),
  plot_ly(shared, x = ~Mean, y=~SD, type = "scatter"),
  datatable(shared)
)

# Pizza [ggiraph]

::: {.cell}

```{.r .cell-code}
library(tidyverse)
library(ggthemes)
library(ggiraph)
library(ggthemes)
library(patchwork)
library(cowplot)
library(rayshader)
library(emojifont)
library(gridExtra)
library(extrafont)

:::

Pizza Ratings

A #tidyTuesday on pizza shop ratings data. The data come from a variety of sources; it is price, ratings, and similar data for pizza restaurants. The actual contents vary depending on the data source. I will begin by loading the data and summarizing what data seem to be available so that we can figure out what we can do with it. Let’s see what we have; NB: there are three datasets, I chose one.

Code
pizza_datafiniti <- readr::read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2019/2019-10-01/pizza_datafiniti.csv")
Code
summary(pizza_datafiniti)
     name             address              city             country         
 Length:10000       Length:10000       Length:10000       Length:10000      
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
   province            latitude       longitude        categories       
 Length:10000       Min.   :21.42   Min.   :-157.80   Length:10000      
 Class :character   1st Qu.:34.42   1st Qu.:-104.80   Class :character  
 Mode  :character   Median :40.12   Median : -82.91   Mode  :character  
                    Mean   :38.37   Mean   : -90.06                     
                    3rd Qu.:40.91   3rd Qu.: -75.19                     
                    Max.   :64.85   Max.   : -71.95                     
 price_range_min  price_range_max
 Min.   : 0.000   Min.   : 7.00  
 1st Qu.: 0.000   1st Qu.:25.00  
 Median : 0.000   Median :25.00  
 Mean   : 4.655   Mean   :27.76  
 3rd Qu.: 0.000   3rd Qu.:25.00  
 Max.   :50.000   Max.   :55.00  

I will use this data; it contains some Oregon pizzarias.

Code
pizza_datafiniti %>% filter(province=="OR")
# A tibble: 122 × 10
   name             address city  country province latitude longitude categories
   <chr>            <chr>   <chr> <chr>   <chr>       <dbl>     <dbl> <chr>     
 1 Coburg Pizza Co… 1710 C… Spri… US      OR           44.1     -123. Restauran…
 2 Coburg Pizza Co… 1710 C… Spri… US      OR           44.1     -123. Restauran…
 3 Coburg Pizza Co… 1710 C… Spri… US      OR           44.1     -123. Restauran…
 4 Coburg Pizza Co… 1710 C… Spri… US      OR           44.1     -123. Restauran…
 5 Coburg Pizza Co… 1710 C… Spri… US      OR           44.1     -123. Restauran…
 6 Coburg Pizza Co… 1710 C… Spri… US      OR           44.1     -123. Restauran…
 7 Coburg Pizza Co… 1710 C… Spri… US      OR           44.1     -123. Restauran…
 8 Coburg Pizza Co… 1710 C… Spri… US      OR           44.1     -123. Restauran…
 9 Coburg Pizza Co… 1710 C… Spri… US      OR           44.1     -123. Restauran…
10 Coburg Pizza Co… 1710 C… Spri… US      OR           44.1     -123. Restauran…
# ℹ 112 more rows
# ℹ 2 more variables: price_range_min <dbl>, price_range_max <dbl>

For the first plot, let me show what is going on in Oregon.

Code
pizzaData <- pizza_datafiniti %>% 
  filter(province=="OR") %>% 
  group_by(name) %>% 
  mutate(Tcount = n(), 
         PriceAvg = mean(price_range_max - price_range_min)) %>% 
  ungroup()
p <- pizzaData %>% ggplot(.) +
  aes(x=reorder(name,PriceAvg), 
      fill=PriceAvg) + 
  geom_bar() + 
  coord_flip() + 
  labs(x="Pizza Restaurant", 
       y="Count", 
       title="Oregon Pizzerias", 
       caption = "data from #tidyTuesday; sorted by Average Price")
p

Now I want a map.

Code
pizzaData$group <- 44
states <- map_data("state")
OR.df <- subset(states, region == "oregon")
OR_base <- ggplot(data = OR.df, mapping = aes(x = long, y = lat, group = group)) +
geom_polygon(color = "black", fill = "gray") + labs(title="Oregon Pizzerias")
OR_base

Code
OR.Pizza <- OR_base + geom_point(data = pizzaData, aes(y=latitude, x=longitude, group=group), color = "red")
OR.Pizza

Combine them into one picture

Code
grid.arrange(p,OR.Pizza, ncol=2)

Now to use a girafe to put this together interactively.

Code
theme_set(theme_minimal())
pizzaData <- pizzaData %>% mutate(name = str_remove(name, "['``]"))
# Build the barplot
gg1 <- ggplot(pizzaData, aes(x=reorder(name,PriceAvg), fill=PriceAvg)) +
  geom_bar_interactive(aes(x = name, tooltip = name, data_id = name))  + 
  coord_flip() + scale_fill_viridis_c() + theme(axis.text=element_text(size=8)) +
  labs(x="Pizza Restaurant", y="Count", caption = "data from #tidyTuesday", sub="sorted by Average Price") 
# Build the map
gg2 <- ggplot(data = OR.df, mapping = aes(x = long, y = lat, group = group)) +
  geom_polygon(fill=gray(0.95)) + 
  geom_text_interactive(data = pizzaData, aes(y=latitude, x=longitude, label=emoji('pizza'), tooltip = name, data_id = name), family='EmojiOne', size=2, color="orange") + 
  scale_color_viridis_c(guide=FALSE) +
  theme_nothing()
# Bind them together with girafe
a <- girafe( code = print(gg1 / gg2 + plot_annotation(title="Pizza in Oregon")))
library(widgetframe)
frameWidget(a, width = "100%", height = "100%")

The formatting of this is quite off. For some reason, the frame doesn’t control scroll and overplots.

Rayshader

And a mini-rayshader. This is a complete lift and replace of the vignette after calculating the Price Midpoint.

Code
states <- map_data("state")
PDF <- pizza_datafiniti %>% mutate(Price.Midpoint = (price_range_min + price_range_max / 2))
mtplot <- ggplot(data = states, mapping = aes(x = long, y = lat)) +
  geom_polygon(color = "black", fill = "gray") + 
  geom_point(data=PDF, aes(x = longitude, y = latitude, color = Price.Midpoint)) + 
  scale_color_viridis_c() + theme_minimal()
plot_gg(mtplot, width = 3.5, multicore = TRUE, windowsize = c(1200, 1200), 
        zoom = 0.5, phi = 35, theta = 30, sunangle = 15, soliddepth = -20)
render_snapshot(clear = TRUE)

Addendum: the function

Borrowed from Ted Laderas.

Code
library(DT)
items <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-05-05/items.csv')
datatable(head(items))

A clever function

Ted Laderas [@laderast on Twitter] wrote a function to present the ten most expensive items in a category for the items dataset. You can find his repo for this here.. I will change rows 3 and 9. Line 3 adds the new argument to the function and line 9 carries the variable defined in the argument into top_n.

Ted’s Original

Code
library(ggimage); library(gt)
library(tidyverse)
most_expensive <- function(category_name=NULL, price_category=buy_value){
  if(!is.null(category_name)){
    items <- items %>%
      filter(category == category_name)
  }
items %>% 
  top_n(10, {{price_category}}) %>%
  arrange(desc({{price_category}})) %>%
  select(name, sell_value, buy_value, category, image=image_url) %>%
  gt() %>%
   text_transform(
    locations = cells_body(vars(image)),
    fn = function(x) {
      web_image(
        url = x,
        height = 50
      )
    }
  )
}

My Modification

Code
library(ggimage); library(gt)
library(tidyverse)
most_expensive <- function(category_name=NULL, price_category=buy_value, n=10){
  if(!is.null(category_name)){
    items <- items %>%
      filter(category == category_name)
  }
items %>% 
  top_n(n, {{price_category}}) %>%
  arrange(desc({{price_category}})) %>%
  select(name, image=image_url, sell_value, buy_value, category) %>%
  gt() %>%
   text_transform(
    locations = cells_body(vars(image)),
    fn = function(x) {
      web_image(
        url = x,
        height = 50
      )
    }
  )
}

Making a Table

Code
most_expensive() %>%  
  tab_header(title = "Most Expensive Items in Animal Crossing By Buy Price") %>%  
  cols_label(
    name = "Item",
    sell_value = "Sale Price",
    buy_value = "Buy Price",
    category = "Item Type",
    image = "Picture"
  ) %>%   tab_spanner(
    label = "Prices",
    columns = c(buy_value, sell_value)
  )
Most Expensive Items in Animal Crossing By Buy Price
Item Picture Prices Item Type
Buy Price Sale Price
Royal Crown 1200000 300000 Hats
Crown 1000000 250000 Hats
Gold Armor 320000 80000 Dresses
Golden Casket 320000 80000 Furniture
Grand Piano 260000 65000 Furniture
Golden Toilet 240000 60000 Furniture
Blue Steel Staircase 228000 NA Furniture
Iron Bridge 228000 NA Furniture
Red Steel Staircase 228000 NA Furniture
Red Zen Bridge 228000 NA Furniture
Zen Bridge 228000 NA Furniture

The Question

Code
most_expensive("Hats") %>%  tab_header(title = "Most Expensive Items in Animal Crossing By Buy Price") %>%  cols_label(
    name = "Item",
    sell_value = "Sale Price",
    buy_value = "Buy Price",
    category = "Item Type",
    image = "Picture"
) %>%   tab_spanner(
    label = "Prices",
    columns = c(buy_value, sell_value)
)
Most Expensive Items in Animal Crossing By Buy Price
Item Picture Prices Item Type
Buy Price Sale Price
Royal Crown 1200000 300000 Hats
Crown 1000000 250000 Hats
Gold Helmet 200000 50000 Hats
Blue Rose Crown 48000 12000 Hats
Gold Rose Crown 48000 12000 Hats
Snowperson Head 28000 7000 Hats
Knight's Helmet 15000 3750 Hats
Dark Cosmos Crown 13440 3360 Hats
Chic Rose Crown 11520 2880 Hats
Purple Hyacinth Crown 11520 2880 Hats
Purple Pansy Crown 11520 2880 Hats
Purple Windflower Crown 11520 2880 Hats
Simple Mum Crown 11520 2880 Hats

Fossils?

Code
most_expensive("Fossils", price_category = sell_value, n=65) %>%  tab_header(title = "Most Expensive Items in Animal Crossing By Buy Price") %>%  cols_label(
    name = "Item",
    sell_value = "Sale Price",
    buy_value = "Buy Price",
    category = "Item Type",
    image = "Picture"
) %>%   tab_spanner(
    label = "Prices",
    columns = c(buy_value, sell_value)
)
Most Expensive Items in Animal Crossing By Buy Price
Item Picture Prices Item Type
Buy Price Sale Price
Brachio Skull NA 6000 Fossils
T. Rex Skull NA 6000 Fossils
Brachio Chest NA 5500 Fossils
Brachio Tail NA 5500 Fossils
Dimetrodon Skull NA 5500 Fossils
Right Megalo Side NA 5500 Fossils
T. Rex Torso NA 5500 Fossils
Tricera Skull NA 5500 Fossils
Brachio Pelvis NA 5000 Fossils
Dimetrodon Torso NA 5000 Fossils
Diplo Skull NA 5000 Fossils
Diplo Tail NA 5000 Fossils
Left Quetzal Wing NA 5000 Fossils
Right Quetzal Wing NA 5000 Fossils
Stego Skull NA 5000 Fossils
T. Rex Tail NA 5000 Fossils
Tricera Torso NA 5000 Fossils
Diplo Neck NA 4500 Fossils
Diplo Pelvis NA 4500 Fossils
Left Ptera Wing NA 4500 Fossils
Megacero Skull NA 4500 Fossils
Plesio Body NA 4500 Fossils
Plesio Tail NA 4500 Fossils
Quetzal Torso NA 4500 Fossils
Right Ptera Wing NA 4500 Fossils
Stego Torso NA 4500 Fossils
Tricera Tail NA 4500 Fossils
Archelon Skull NA 4000 Fossils
Diplo Chest NA 4000 Fossils
Diplo Tail Tip NA 4000 Fossils
Iguanodon Skull NA 4000 Fossils
Left Megalo Side NA 4000 Fossils
Pachy Skull NA 4000 Fossils
Plesio Skull NA 4000 Fossils
Ptera Body NA 4000 Fossils
Spino Skull NA 4000 Fossils
Stego Tail NA 4000 Fossils
Ankylo Skull NA 3500 Fossils
Archelon Tail NA 3500 Fossils
Dunkleosteus NA 3500 Fossils
Iguanodon Torso NA 3500 Fossils
Megacero Torso NA 3500 Fossils
Pachy Tail NA 3500 Fossils
Parasaur Skull NA 3500 Fossils
Ankylo Torso NA 3000 Fossils
Deinony Torso NA 3000 Fossils
Iguanodon Tail NA 3000 Fossils
Mammoth Skull NA 3000 Fossils
Megacero Tail NA 3000 Fossils
Parasaur Torso NA 3000 Fossils
Spino Torso NA 3000 Fossils
Ankylo Tail NA 2500 Fossils
Deinony Tail NA 2500 Fossils
Mammoth Torso NA 2500 Fossils
Ophthalmo Skull NA 2500 Fossils
Opthalmo Skull NA 2500 Fossils
Opthalmo Torso NA 2500 Fossils
Parasaur Tail NA 2500 Fossils
Sabertooth Skull NA 2500 Fossils
Spino Tail NA 2500 Fossils
Acanthostega NA 2000 Fossils
Anomalocaris NA 2000 Fossils
Eusthenopteron NA 2000 Fossils
Ophthalmo Torso NA 2000 Fossils
Sabertooth Tail NA 2000 Fossils