Wolfram Computation Meets Knowledge

Accessing Monarch Biodiversity Data with New Wolfram Language Functions

Earth has experienced five major extinctions since life first appeared almost four billion years ago. The sixth is happening right now; the current extinction rate is between one hundred and one thousand times greater than what it was before 1800.

Despite the alarming extinction rate, it’s easier than ever to document biodiversity with the help of the Wolfram Language. Using the monarch butterfly as an example, I will explore the new biodiversity data access functions in the Wolfram Function Repository and how they can help you join a community of thousands of citizen scientists from iNaturalist in preserving biodiversity.

I developed a set of biodiversity data access functions—INaturalistImport, INaturalistSearch, GBIFImport, GBIFSearch and GloBIData—to better enable researchers, citizen scientists, educators and Wolfram users to access global biodiversity data from the iNaturalist community, Global Biodiversity Information Facility (GBIF) and Global Biotic Interactions (GloBI).

Becoming a Citizen Scientist

Crowdsourced data and observations from citizen scientists can be valuable for researchers, allowing global communities to track biodiversity worldwide. Over the last year, thanks to the iNaturalist app, I observed and identified hundreds of different species during hikes and got invaluable feedback from many different experts, each specializing in some particular life form. There are currently more than 290,000 different species recorded on the platform, a handful of which I was the first iNaturalist user to observe.

A great starting place as a beginner citizen scientist is to learn more about the species you want to find. If you know the scientific name—for the monarch butterfly, it’s Danaus plexippusWikidataSearch allows you to better visualize what you’re looking for:

WikidataSearch
&#10005

WikidataSearch["Danaus Plexippus"]

ImageResize
&#10005

ImageResize[Import@First@WikidataData[ExternalIdentifier["WikidataID", "Q212398", <|"Label" -> "Danaus plexippus", "Description" -> "milkweed butterfly in the family Nymphalidae"|>], "Image"], 600]

I can also check for some more specific properties, like wingspan, with WikidataData:

WikidataData
&#10005

WikidataData[
 ExternalIdentifier["WikidataID", 
  "Q212398", <|"Label" -> "Danaus plexippus", 
   "Description" -> "milkweed butterfly in the family Nymphalidae"|>],
  ExternalIdentifier["WikidataID", 
  "P2050", <|"Label" -> "wingspan", 
   "Description" -> 
    "distance from one wingtip to the other, of an airplane or an \
animal"|>]]

Or ask for their maximum speed using its species entity (CTRL+=):

\!\(NamespaceBox
&#10005

\!\(
NamespaceBox["WolframAlphaQueryParseResults",
RowBox[{"CloudGet", "[", "$Failed", "]"}],
BaseStyle->{Deployed -> True},
DeleteWithContents->True,
Editable->False,
SelectWithContents->True]\)

Note that Anosia is a synonym name for the taxon of the genus Danau:

Entity
&#10005

Entity["Species", "Species:AnosiaPlexippus"][
 EntityProperty["Species", "MaximumSpeed"]]

Let’s convert the units into more familiar speed formats:

UnitConvert
&#10005

UnitConvert[Quantity[18000000, ("Millimeters")/("Hours")], 
 Quantity[1, (("Kilometers")/("Hours"))]]

N@UnitConvert
&#10005

N@UnitConvert[Quantity[18000000, ("Millimeters")/("Hours")], 
  Quantity[1, (("Miles")/("Hours"))]]

Mapping Species Geography

Monarch butterflies are one of the most amazing insects, capable of flying thousands of kilometers. The majority of them overwinter in a specific region of Mexico and fly back toward the north of North America in four generations (around a two-month period). During the end of summer when the sunlight hours decrease and temperatures drop, a different generation of monarch flies all the way down to Mexico in a single generation.

Using INaturalistSearch, I can easily create a map with all of the North American iNaturalist observations since yesterday:

ResourceFunction
&#10005

ResourceFunction["INaturalistSearch"]

monarchData = ResourceFunction
&#10005

monarchData = ResourceFunction[
ResourceObject[
Association[
     "Name" -> "INaturalistSearch", 
      "ShortName" -> "INaturalistSearch", 
      "UUID" -> "52cc24aa-c5a6-47ca-998f-3e5c08b4ed53", 
      "ResourceType" -> "Function", "Version" -> "1.0.0", 
      "Description" -> "Search for iNaturalist observations using the \
iNaturalist API", 
      "RepositoryLocation" -> URL[
       "https://www.wolframcloud.com/objects/resourcesystem/api/1.0"],
       "SymbolName" -> "FunctionRepository`$\
d5b9fb4680a7411e9344c42a8625a99d`INaturalistSearch", 
      "FunctionLocation" -> CloudObject[
       "https://www.wolframcloud.com/obj/1181e589-ac03-4b62-b411-\
3a30b643b8bf"]], ResourceSystemBase -> Automatic]][ 
   "Danaus plexippus" , 
   "ObservationGeoRange" -> 
    GeoBounds[Entity["GeographicRegion", "NorthAmerica"]], 
   "ObservationDateRange" -> {Yesterday, Today}, 
   "QualityGrade" -> "Research" ];

The output is a large Dataset; we can check the first observation, for example:

First@monarchData
&#10005

First@monarchData

Let’s use a custom GeoMarker icon image to represent each recorded observation of the monarch butterfly:

monarchIcon = Rotate
&#10005

monarchIcon = Rotate[CloudGet[$Failed], -30 Degree];

GeoGraphics
&#10005

GeoGraphics[{GeoMarker[monarchData[All, "GeoPosition"], monarchIcon, 
   "Scale" -> 0.06]}, ImageSize -> 500, 
 GeoRange -> 
  GeoBounds[
   Polygon[{Entity["Country", "UnitedStates"], 
     Entity["Country", "Mexico"]}]]]

Next, I would like to visualize their annual migration. But I want to first check the total count of observations over a year (2019), because if there are too many (over one thousand observations), then we should avoid using INaturalistSearch multiple times (See the iNaturalist API recommended practices page for details):

ResourceFunction
&#10005

ResourceFunction[
ResourceObject[
Association[
   "Name" -> "INaturalistSearch", "ShortName" -> "INaturalistSearch", 
    "UUID" -> "52cc24aa-c5a6-47ca-998f-3e5c08b4ed53", 
    "ResourceType" -> "Function", "Version" -> "1.0.0", 
    "Description" -> "Search for iNaturalist observations using the \
iNaturalist API", 
    "RepositoryLocation" -> URL[
     "https://www.wolframcloud.com/objects/resourcesystem/api/1.0"], 
    "SymbolName" -> "FunctionRepository`$\
d5b9fb4680a7411e9344c42a8625a99d`INaturalistSearch", 
    "FunctionLocation" -> CloudObject[
     "https://www.wolframcloud.com/obj/1181e589-ac03-4b62-b411-\
3a30b643b8bf"]], 
   ResourceSystemBase -> Automatic]][ "Danaus plexippus" , "Count", 
 "ObservationGeoRange" -> 
  GeoBounds[Entity["GeographicRegion", "NorthAmerica"]], 
 "ObservationDateRange" -> {DateObject[{2019, 1, 1}], 
   DateObject[{2019, 12, 31}]} ]

There are over thirty thousand observations! So it is clear that I will need to use either INaturalistImport or GBIFImport.

As a first step, we need to go to the GBIF site and download a large dataset as a CSV file.

Now that we have the file, we are ready to import the data using GBIFImport:

monarchData = ResourceFunction
&#10005

monarchData = ResourceFunction[
ResourceObject[
Association[
      "Name" -> "GBIFImport", "ShortName" -> "GBIFImport", 
       "UUID" -> "0370613e-18f0-48cd-b269-65c65ff3f5f3", 
       "ResourceType" -> "Function", "Version" -> "1.0.0", 
       "Description" -> "Import occurrences from Global Biodiversity \
Information Facility (GBIF) data", 
       "RepositoryLocation" -> URL[
        "https://www.wolframcloud.com/objects/resourcesystem/api/1.0"]\
, "SymbolName" -> "FunctionRepository`$\
fce501ba07a74ffa971862153240e965`GBIFImport", 
       "FunctionLocation" -> CloudObject[
        "https://www.wolframcloud.com/obj/439b1008-50bf-41b2-b22e-\
07113533d4d0"]], ResourceSystemBase -> Automatic]][
    "NorthAmericaOccurrences.csv"] // Quiet;

GBIF gathers data from multiple institutions; by using Counts on the "InstitutionCode" property, we can see that the vast majority of observations on monarch butterflies are coming from iNaturalist users:

Counts@monarchData
&#10005

Counts@monarchData[All, "InstitutionCode"]

We can create a DateHistogram to see when there are more observations of monarchs:

observationDates = monarchData
&#10005

observationDates = monarchData[All, "Date"];

DateHistogram
&#10005

DateHistogram[observationDates, "Day", ColorFunction -> "SolarColors",
  ImageSize -> Large, PlotTheme -> "Detailed"]

Not surprisingly, observations rise in March when the northward migration starts, and peak in August when monarch butterflies are highly present in gardens of the northern USwhere the density of iNaturalist users is higher.

As a fun experiment, we can compare daily page hits for two related Wikipedia articles (“Monarch butterfly” and “Monarch butterfly migration”) using WikipediaData and DateListLogPlot:

monarchButterfly = WikipediaData
&#10005

monarchButterfly = WikipediaData["Monarch butterfly", "DailyPageHits"];
monarchMigration = 
  WikipediaData["Monarch butterfly migration", "DailyPageHits"];

DateListLogPlot
&#10005

DateListLogPlot[{monarchButterfly, monarchMigration}, 
 PlotRange -> {{DateObject[{2019}], DateObject[{2019, 12, 31}]}, All},
  PlotLegends -> {"Monarch butterfly", "Monarch butterfly migration"},
  ImageSize -> 600, PlotTheme -> "Detailed"]

The “Monarch butterfly” article gets a higher number of daily visits during July and August when the presence of monarchs is higher in the US. We can also observe two peaks for the “Monarch butterfly migration” article corresponding to the start of northward migration in March and another in September when they fly southwest toward Mexico.

It seems that people search for info about the butterflies when they hear about the start of the northward migration, but it isn’t until summer when they actively go out and record their observations of monarchs.

If we compare this with page views of the Spanish version of the article, the graph shows a decrease during summer when monarchs are mainly in the US:

monarchButterfly = WikipediaData
&#10005

monarchButterfly = 
  WikipediaData["Monarch butterfly", "DailyPageHits", 
   Language -> {Entity["Language", "English"] -> 
      Entity["Language", "Spanish"]}];

DateListLogPlot
&#10005

DateListLogPlot[monarchButterfly, 
 PlotRange -> {{DateObject[{2019}], DateObject[{2019, 12, 31}]}, All},
  PlotLegends -> {"Monarch butterfly (Spanish page)"}, 
 ImageSize -> 600, PlotTheme -> "Detailed"]

To create the map animation of the migration, we need to group the observations by date. This is easily achieved in the dataset using GroupBy:

observationsPerDay = monarchData
&#10005

observationsPerDay = monarchData[GroupBy["Date"]];

Next, we need to create some plotting utilities to make a more interesting map:

colorDateFunction
&#10005

colorDateFunction[date_DateObject] := 
 ColorData["BrightBands"][
  ToExpression@DateValue[date, "ISOYearDayShort"]/365]

The previous function will allow us to represent observations on the map with different colors that correspond to their observation dates. We can create its corresponding legend using SwatchLegend:

legend = SwatchLegend
&#10005

legend = SwatchLegend[
  Map[ColorData["BrightBands"][(# - 0.5)/12] &, Range[12]], 
  Map[Style[#, FontSize -> 18] &, {"January", "February", "March", 
    "April", "May", "June", "July", "August", "September", "October", 
    "November", "December"}], 
  LegendFunction -> (Framed[#, RoundingRadius -> 5, 
      FrameStyle -> White, Background -> White] &), 
  LegendLayout -> {"Column", 2}, LegendMarkerSize -> 15, 
  LegendMarkers -> "Bubble"]

We also need to get the geographic positions associated with each day:

getGeoPositionsPerDate
&#10005

getGeoPositionsPerDate[date_DateObject] := 
 KeyTake[observationsPerDay, date][[1, All, "GeoPosition"]]

The above one-liner will get a list of GeoPosition objects corresponding to the observations of a specific date.

The following function will get a list of all days since the start of 2019 until the specified date:

getDates
&#10005

getDates[date_DateObject] := DateRange[DateObject[{2019, 1, 1}], date]

Finally, we can add some random tumbling to the monarch icons using Rotate and RandomReal, to give them a little life and make them look like they’re flying:

mapNorthMigrationByDate
&#10005

mapNorthMigrationByDate[date_] := 
 Rasterize@
  GeoGraphics[
   Join[Flatten@
     Map[{colorDateFunction[#], Opacity[0.8], PointSize[.009], 
        Point[getGeoPositionsPerDate[#]]} &, getDates[date]], {Map[
      GeoMarker[#, Rotate[monarchIcon, RandomReal[{-20, 20}] Degree], 
        "Scale" -> 0.08] &, getGeoPositionsPerDate[date]]},
    {Black, 
     Inset[Framed[
       Style["Observation Date\n" <> DateString[date, "ISODate"], 
        FontFamily -> "Helvetica", FontSize -> 22], 
       FrameStyle -> White, RoundingRadius -> 4, Background -> White],
       Scaled[{0.11, 0.88}]],
     Black,
     Inset[legend, Scaled[{0.15, 0.2}]]}],
   ImageSize -> {1010, 480},
   GeoRange -> {{14, 48}, {-124, -67}},
   GeoBackground -> "StreetMapNoLabels"]

Let’s try a map for a particular day:

mapNorthMigrationByDate
&#10005

mapNorthMigrationByDate[DateObject[{2019, 4, 17}]]

We can do the same for the migration toward the south that starts in September by rotating the icons top to bottom:

mapSouthMigrationByDate
&#10005

mapSouthMigrationByDate[date_] := 
 Rasterize@
  GeoGraphics[
   Join[Flatten@
     Map[{colorDateFunction[#], Opacity[0.8], PointSize[.009], 
        Point[getGeoPositionsPerDate[#]]} &, getDates[date]], {Map[
      GeoMarker[#, 
        Rotate[monarchIcon, RandomReal[{-170, -210}] Degree], 
        "Scale" -> 0.08] &, getGeoPositionsPerDate[date]]},
    {Black, 
     Inset[Framed[
       Style["Observation Date\n" <> DateString[date, "ISODate"], 
        FontFamily -> "Helvetica", FontSize -> 22], 
       FrameStyle -> White, RoundingRadius -> 4, Background -> White],
       Scaled[{0.11, 0.88}]],
     Black,
     Inset[legend, Scaled[{0.15, 0.2}]]}],
   ImageSize -> {1010, 480},
   GeoRange -> {{14, 48}, {-124, -67}},
   GeoBackground -> "StreetMapNoLabels"]

Cool! Now we only need to create one map for each day of the year and use Export to obtain an APNG of each one (note that it will take a while to create that many frames with 22,000 observations):

Export
&#10005

Export["MonarchMigration.apng", 
 Join[Map[mapNorthMigrationByDate , 
   DateRange[DateObject[{2019, 1, 1}], DateObject[{2019, 8, 31}]]], 
  Map[mapSouthMigrationByDate , 
   DateRange[DateObject[{2019, 9, 1}], DateObject[{2019, 12, 31}]]]], 
 "DisplayDurations" -> 0.5, "AnimationRepetitions" -> Infinity]

Classifying Male and Female Specimens

Let’s try some machine learning techniques on monarch butterflies. The following photo was taken by one of my colleagues, Cassidy, who has a beautiful garden in Champaign, Illinois, with many milkweed plants, the feeding source for monarch caterpillars:

CloudGet
&#10005

CloudGet[$Failed]

I will start with something simple, ImageIdentify:

ImageIdentify
&#10005

ImageIdentify[CloudGet[$Failed], 
 Entity["Species", "Class:Insecta"], 10, "Probability"]

alt

Some iNaturalist observations have annotations attached. For example, users can annotate a specimen’s sex as female or male. This info might be useful to scientists studying the ratio of females to males. But in reality, only a small percentage of users specify whether the monarch they observed was a male or a female. If we could automate these manual annotations of a specimen’s sex, we could create a much larger dataset in order to study female vs. male populations. Let’s try to gather images of monarch females and males and train our own sex classifier with Classify:

males = Map
&#10005

males = Map[Import, Normal[ResourceFunction[
ResourceObject[
Association[
         "Name" -> "INaturalistSearch", 
          "ShortName" -> "INaturalistSearch", 
          "UUID" -> "52cc24aa-c5a6-47ca-998f-3e5c08b4ed53", 
          "ResourceType" -> "Function", "Version" -> "1.0.0", 
          "Description" -> "Search for iNaturalist observations using \
the iNaturalist API", 
          "RepositoryLocation" -> URL[
           "https://www.wolframcloud.com/objects/resourcesystem/api/1.\
0"], "SymbolName" -> "FunctionRepository`$\
d5b9fb4680a7411e9344c42a8625a99d`INaturalistSearch", 
          "FunctionLocation" -> CloudObject[
           "https://www.wolframcloud.com/obj/1181e589-ac03-4b62-b411-\
3a30b643b8bf"]], ResourceSystemBase -> Automatic]][ 
       "Danaus plexippus" , "Sex" -> "Male" , "HasImage" -> True, 
       "QualityGrade" -> "Research" ][All, "SquareImageURL"]]] // 
   Quiet;

females = Map
&#10005

females = Map[Import, Normal[ResourceFunction[
ResourceObject[
Association[
         "Name" -> "INaturalistSearch", 
          "ShortName" -> "INaturalistSearch", 
          "UUID" -> "52cc24aa-c5a6-47ca-998f-3e5c08b4ed53", 
          "ResourceType" -> "Function", "Version" -> "1.0.0", 
          "Description" -> "Search for iNaturalist observations using \
the iNaturalist API", 
          "RepositoryLocation" -> URL[
           "https://www.wolframcloud.com/objects/resourcesystem/api/1.\
0"], "SymbolName" -> "FunctionRepository`$\
d5b9fb4680a7411e9344c42a8625a99d`INaturalistSearch", 
          "FunctionLocation" -> CloudObject[
           "https://www.wolframcloud.com/obj/1181e589-ac03-4b62-b411-\
3a30b643b8bf"]], ResourceSystemBase -> Automatic]][ 
       "Danaus plexippus" , "Sex" -> "Female" , "HasImage" -> True , 
       "QualityGrade" -> "Research"][All, "SquareImageURL"]]] // 
   Quiet;

Create a dataset with a randomized list of key values:

dataset = RandomSample@Flatten
&#10005

dataset = 
  RandomSample@
   Flatten[Thread /@ Thread[{males, females} -> {"Male", "Female"}]];

dataset
&#10005

dataset[[1]]

Separate the dataset into a training set and a test set:

trainingSet = dataset
&#10005

trainingSet = dataset[[;; 150]];
testSet = dataset[[151 ;;]];

Then we can create a classifier using Classify:

c = Classify
&#10005

c = Classify[trainingSet, PerformanceGoal -> "Quality"]

The resulting classifier seems to have a fairly high accuracy of around 75%. For example, I don’t know how to distinguish male from female monarchs without having a reference:

Information
&#10005

Information[c]

Now we can easily plot the confusion matrix of the classifier using the test set:

cm = ClassifierMeasurements
&#10005

cm = ClassifierMeasurements[c, testSet, "ConfusionMatrixPlot"]

Nice! Let’s try a couple of specific examples:

testSet
&#10005

testSet[[2]]

c
&#10005

c[CloudGet[$Failed]]

testSet
&#10005

testSet[[7]]

c
&#10005

c[CloudGet[$Failed]]

It seems to be working decently well with stock images. Let’s try using a photo of a monarch taken by my friend Cassidy in her garden. She told me it was a female—will our classifier confirm her guess?

c
&#10005

c[CloudGet[$Failed], "Probabilities"]

You could try to improve the current classifier by using more images or data augmentation techniques and training a custom neural network. You can start with a neural net model from the Wolfram Neural Net Repository used for image classification problems, like the Squeeze-and-Excitation Net or the Wolfram ImageIdentify Net V1.

Exploring Biotic Interactions

Another way to help you document species is to study what they eat so you know where to find them. Let’s take a closer look at the monarch butterfly’s diet. The Global Biotic Interactions platform has several gigabytes of data containing millions of interactions among species extracted from scientific papers, the Encyclopedia of Life (EOL) and research-grade observations from iNaturalist and GBIF. Here we will explore the food web of monarchs, the animals that feed on milkweed plants (the main diet of monarch caterpillars) and finally, potential monarch parasites:

ResourceFunction
&#10005

ResourceFunction[“GloBIData”, “Description”]

On the first argument of GloBIData, we need to specify the taxon. As an option, we can retrieve specific interaction types:

&#10005

ResourceFunction[
ResourceObject[
Association[
   "Name" -> "GloBIData", "ShortName" -> "GloBIData", 
    "UUID" -> "74041c3a-f659-4059-bba3-258cfc0b24f4", 
    "ResourceType" -> "Function", "Version" -> "1.0.0", 
    "Description" -> "Gives biotic interactions of a specified taxon \
using the Global Biotic Interactions (GloBI) API", 
    "RepositoryLocation" -> URL[
     "https://www.wolframcloud.com/objects/resourcesystem/api/1.0"], 
    "SymbolName" -> "FunctionRepository`$\
df4a3b08bcab4d24a1dd61c57fc78f13`GloBIData", 
    "FunctionLocation" -> CloudObject[
     "https://www.wolframcloud.com/obj/4eb060b6-e49d-4b76-a129-\
fd5409ac61f7"]], {
   ResourceSystemBase -> "https://www.wolframcloud.com/objects/\
resourcesystem/api/1.0"}]]["Danaus Plexippus", 
 "InteractionType" -> "Eats"]

Wow, that’s a huge food web! Its diet contains many species of plants, including a milkweed (genus Asclepias) that larvae feed on and that also appears in the left cluster. Let’s take that information and use it to find out what the milkweed looks like:

\!\(NamespaceBox
&#10005

\!\(
NamespaceBox["WolframAlphaQueryParseResults",
RowBox[{"CloudGet", "[", "$Failed", "]"}],
BaseStyle->{Deployed -> True},
DeleteWithContents->True,
Editable->False,
SelectWithContents->True]\)

Entity
&#10005

Entity["Plant", "Genus:Asclepias"]["Image"]

Last year, during the 2019 Wolfram Summer School in Waltham, Massachusetts, I found a common milkweed during a forest walk near Bentley University. Importing my photo to help other citizen scientists find milkweed—and perhaps monarchs feeding on it—is now straightforward with INaturalistSearch:

ImageResize
&#10005

ImageResize[
 Import@First[
   INaturalistSearch["Asclepias syriaca", "UserLogin" -> "jofree"][
    All, "ImageURL"]], 600]

Monarch butterflies have high concentrations of a toxic molecule extracted from milkweed plants. This prevents them from being eaten by many potential predators. If a predator eats them, the toxins cause it to get sick or even suffer cardiac arrest.

With GloBIData, I can also do the reverse and ask for moths and butterflies feeding on milkweed plants by specifying a "TargetTaxon":

&#10005

ResourceFunction[
ResourceObject[
Association[
   "Name" -> "GloBIData", "ShortName" -> "GloBIData", 
    "UUID" -> "74041c3a-f659-4059-bba3-258cfc0b24f4", 
    "ResourceType" -> "Function", "Version" -> "1.0.0", 
    "Description" -> "Gives biotic interactions of a specified taxon \
using the Global Biotic Interactions (GloBI) API", 
    "RepositoryLocation" -> URL[
     "https://www.wolframcloud.com/objects/resourcesystem/api/1.0"], 
    "SymbolName" -> "FunctionRepository`$\
df4a3b08bcab4d24a1dd61c57fc78f13`GloBIData", 
    "FunctionLocation" -> CloudObject[
     "https://www.wolframcloud.com/obj/4eb060b6-e49d-4b76-a129-\
fd5409ac61f7"]], {
   ResourceSystemBase -> "https://www.wolframcloud.com/objects/\
resourcesystem/api/1.0"}]][Entity["Plant", "Genus:Asclepias"], 
 "InteractionType" -> "EatenBy", 
 "TargetTaxon" -> Entity["Species", "Order:Lepidoptera"]]

Although the monarch larvae are the main consumers of milkweed, many other types of butterflies and moths also like it.

I can also get a taxonomic graph of organisms that are parasites of monarchs using the "HasParasite" interaction type:

&#10005

ResourceFunction[
ResourceObject[
Association[
   "Name" -> "GloBIData", "ShortName" -> "GloBIData", 
    "UUID" -> "74041c3a-f659-4059-bba3-258cfc0b24f4", 
    "ResourceType" -> "Function", "Version" -> "1.0.0", 
    "Description" -> "Gives biotic interactions of a specified taxon \
using the Global Biotic Interactions (GloBI) API", 
    "RepositoryLocation" -> URL[
     "https://www.wolframcloud.com/objects/resourcesystem/api/1.0"], 
    "SymbolName" -> "FunctionRepository`$\
df4a3b08bcab4d24a1dd61c57fc78f13`GloBIData", 
    "FunctionLocation" -> CloudObject[
     "https://www.wolframcloud.com/obj/4eb060b6-e49d-4b76-a129-\
fd5409ac61f7"]], {
   ResourceSystemBase -> "https://www.wolframcloud.com/objects/\
resourcesystem/api/1.0"}]]["Danaus Plexippus", 
 "InteractionType" -> "HasParasite"]

Monarch Butterfly Biosphere

During winter, monarchs travel by the thousands to mountain forests in Mexico. One of the most famous wintering sanctuaries is the Monarch Butterfly Biosphere Reserve, a World Heritage Site in Michoacán, which is about 60 miles northwest of Mexico City. Let’s find the exact location using WikidataSearch:

WikidataSearch
&#10005

Computing a More Biodiverse Future

WikidataSearch[“monarch butterfly biosphere”]

winteringLocation = First@WikidataData
&#10005

winteringLocation = 
  First@WikidataData[
    ExternalIdentifier["WikidataID", 
     "Q852546", <|"Label" -> "Monarch Butterfly Biosphere Reserve", 
      "Description" -> "biosphere reserve"|>], 
    ExternalIdentifier["WikidataID", 
     "P625", <|"Label" -> "coordinate location", 
      "Description" -> 
       "geocoordinates of the subject. For Earth, please note that \
only WGS84 coordinating system is supported at the moment"|>]];

We can easily see the massive distance that, for example, Cassidy’s monarch (born in Champaign) will need to fly:

GeoDistance
&#10005

GeoDistance[Entity["City", {"Champaign", "Illinois", "UnitedStates"}], winteringLocation]

We can also plot the trip with GeoPath and Arrow:

GeoGraphics
&#10005

GeoGraphics[{Red, Thick, Arrow@GeoPath[{Entity["City", {"Champaign", "Illinois", "UnitedStates"}], winteringLocation}]}, ImageSize -> 600, GeoBackground -> "Satellite", GeoRange -> Polygon[{Entity["Country", "UnitedStates"], Entity["Country", "Mexico"]}]]

The Wolfram Language is also capable of getting satellite images. For example, we can look at the Biosphere Reserve using GeoImage:

GeoImage
&#10005

GeoImage[winteringLocation, GeoServer -> "DigitalGlobe"]

As a final exploration, we can also check the observations of threatened species near the previous location using GeoDisk and INaturalistSearch:

threatenedSpecies = ResourceFunction
&#10005

threatenedSpecies = ResourceFunction[ResourceObject[Association["Name" -> "INaturalistSearch", 
      "ShortName" -> "INaturalistSearch", 
      "UUID" -> "52cc24aa-c5a6-47ca-998f-3e5c08b4ed53", 
      "ResourceType" -> "Function", "Version" -> "1.0.0", 
      "Description" -> "Search for iNaturalist observations using the \
iNaturalist API", 
      "RepositoryLocation" -> URL[
       "https://www.wolframcloud.com/objects/resourcesystem/api/1.0"],
       "SymbolName" -> "FunctionRepository`$\
d5b9fb4680a7411e9344c42a8625a99d`INaturalistSearch", 
      "FunctionLocation" -> CloudObject[
       "https://www.wolframcloud.com/obj/1181e589-ac03-4b62-b411-\
3a30b643b8bf"]], ResourceSystemBase -> Automatic]][All, 
   "ObservationGeoRange" -> 
    GeoBounds[GeoDisk[winteringLocation, Quantity[3, "Kilometers"]]], 
   "Threatened" -> True,
   "QualityGrade" -> "Research",
   "HasImage" -> True];

For a more visual understanding, we can create a PieChart of the different types of threatened life forms from the previous list using the "IconicTaxon" property and Counts:

PieChart
&#10005

PieChart[Counts@
  Normal@threatenedSpecies[All, "IconicTaxon"], Sequence[
 SectorOrigin -> {Automatic, 1}, ChartLegends -> Automatic, 
  ChartStyle -> Table[
Part[
ColorData[3, "ColorList"], i], {i, 2, 10}]]]

We can see that the majority of observations are from insects, monarchs in particular. But there are some mushrooms, plants and even a bird and an amphibian counted as threatened species.

Similar to our visual pie chart, an image is worth a thousand words. So let’s create a final ImageCollage out of all the observations of threatened species from the forest sanctuary:

ImageCollage@Map
&#10005

ImageCollage@
 Map[Import, Normal@threatenedSpecies[All, "SquareImageURL"]]

Computing a More Biodiverse Future

I hope you enjoyed these explorations. The number of applications that these new biodiversity data access functions open up is limited only by our imaginations, especially if we start combining them with the variety of built-in Wolfram Language functions. For example, use ScheduledTask to automatically alert you via email with SendMail to the presence of an invasive species in a certain region or new sightings of endangered species in a forest with risk of deforestation.

If you’re looking for more ideas on some of the different ways you can use the Wolfram Language in your adventures, check out my last blog post on the invasive stink bug, as well as my Wolfram Community posts on marine worms and Moon phases and the great horned owl.

Finally, I want to thank Wolfram Research programmer John Cassel for his valuable advice during my journey to bring these functions to life. I would also like to thank professor Gareth Russell of the Department of Ecology at the New Jersey Institute of Technology for his valuable feedback on the functions. The ability to directly import GBIF data is an important step toward the implementation of species distribution modeling using the Wolfram Language. These models are widely used in ecology, and they help to monitor changes in biodiversity and, consequently, help to protect our marvelous planet.

The golden age for becoming a citizen scientist is now. Humanity has counted almost two million distinct life forms—only a small fraction of what scientists believe is still waiting to be found. How many will you find?

Visit Wolfram Community or Wolfram Function Repository to embark on your own computational adventures!

Comments

Join the discussion

!Please enter your comment (at least 5 characters).

!Please enter your name.

!Please enter a valid email address.