Using pyobis to query for known datasets#

Created: 2022-11-23

Updated: 2026-05-04

Author: Mathew Biddle

This notebook uses the pyobis Python package to query the OBIS API for datasets associated with projects funded under the United States Marine Biodiversity Observation Network. The notebook walks through the process for querying the OBIS api for a specific institution, then using the resultant datasets to gather the locations of all the occurrences using the pyobis package.

The US Marine Biodiversity Observation Network (US MBON) is an interagency initiative that seeks to coordinate across sectors and government to characterize biodiversity and understand drivers of change. US MBON represents a broad, collaborative effort to address the need for systematic collection and sharing of marine life information, ensure that information is available for decision-making and management from local to national levels, and document marine biodiversity status and trends in the face of human- and climate-induced change using a range of technologies and approaches. Through the National Oceanographic Partnership Program, NOAA, NASA, Office of Naval Research, and BOEM have invested in US MBON since 2014, most recently announcing new five year projects in 2022.

import pyobis

pyobis.__version__
'1.6.1'

First off, let’s look through the available institutions at OBIS and find the US MBON one.

Hint: We know the name of the institution is United States Marine Biodiversity Observation Network.

import pandas as pd

pd.set_option("max_colwidth", 400)

url = "https://api.obis.org/v3/institute"
institutes = pd.read_json(url)

df_inst = pd.DataFrame.from_records(institutes["results"])
df_inst.dropna(inplace=True)
df_inst["id"] = df_inst["id"].astype(int)

institution = df_inst.loc[
    df_inst["name"].str.contains(
        "United States Marine Biodiversity Observation", na=False
    )
]

institution
id name country records
18 23070 United States Marine Biodiversity Observation Network United States of America 7827785

Well that looks like the institution we’re after!

Using the id we can check it out on the OBIS website: https://obis.org/organization/23070

Yes, that does look like what we want. Now let’s use that id to query OBIS for all associated datasets.

institution_id = institution["id"].tolist()[0]

institution_id
23070

Collect metadata about all of the datasets#

Here we use the institution_id collected from OBIS to query the API for metadata on the datasets associated with that id.

First, we build the API call, then we bring it into a Pandas DataFrame and create additional fields for use later.

import pyobis

query = pyobis.dataset.search(instituteid=institution_id)

df = pd.DataFrame(query.execute())
df_meta = pd.DataFrame.from_records(df["results"])

# create a column for the human readable short name of the datasets
df_meta["short_name"] = df_meta["url"].str.split("=", expand=True)[1]

# create another column to group datasets by, which removes the year from the short names
df_meta["short_name_group"] = df_meta["short_name"].replace(r"\d", "", regex=True)
df_meta.head(5)
id url archive published created updated core extensions statistics extent ... institutes contacts nodes keywords downloads tags disable_extensions records short_name short_name_group
0 a6155d3d-48b8-4778-83aa-776b459a75d1 https://ipt-obis.gbif.us/resource?r=mlpa_kelp_forest_monitoring https://ipt-obis.gbif.us/archive.do?r=mlpa_kelp_forest_monitoring 2025-09-22T18:54:42.000Z 2025-08-06T19:55:42.733Z 2025-10-19T14:52:41.717Z event [measurementorfact, occurrence] {'Event': 123604, 'absence': 26703147, 'dropped': 0, 'Occurrence': 32015548, 'DNADerivedData': 0, 'MeasurementOrFact': 3161067} POLYGON((-124.24377 32.6580521,-117.2472125 32.6580521,-117.2472125 42.00195,-124.24377 42.00195,-124.24377 32.6580521)) ... [{'name': 'United States Marine Biodiversity Observation Network', 'oceanexpert_id': 23070, 'oceanexpert_parent_id': None}, {'name': 'Central & Northern California Ocean Observing System', 'oceanexpert_id': 23204, 'oceanexpert_parent_id': None}] [{'role': None, 'type': 'creator', 'givenname': 'Mark', 'surname': 'Carr', 'organization': 'UC Santa Cruz ', 'position': None, 'email': 'mhcarr@ucsc.edu', 'url': None, 'organization_oceanexpert_id': None, 'type_display': 'Creator'}, {'role': None, 'type': 'creator', 'givenname': 'Jennifer', 'surname': 'Caselle', 'organization': 'UC Santa Barbara', 'position': None, 'email': 'caselle@ucsb.edu',... [{'id': 'b7c47783-a020-4173-b390-7b57c4fa1426', 'name': 'OBIS USA'}] [{'keyword': 'Samplingevent', 'thesaurus': 'GBIF Dataset Type Vocabulary: http://rs.gbif.org/vocabulary/gbif/dataset_type_2015-07-10.xml'}, {'keyword': 'Biological data', 'thesaurus': 'California Ocean Protection Council'}, {'keyword': 'Kelp forest/shallow subtidal', 'thesaurus': 'California Ocean Protection Council'}, {'keyword': 'Rocky reef', 'thesaurus': 'California Ocean Protection Council... [{'year': 2026, 'downloads': 10125, 'records': 1049977035}, {'year': 2025, 'downloads': 5012, 'records': 2332268600}] [https://goosocean.org/eov/fish, https://goosocean.org/eov/invertebrate, https://goosocean.org/eov/macroalgal, https://ror.org/028paz341] None 5312401 mlpa_kelp_forest_monitoring mlpa_kelp_forest_monitoring
1 cfceb150-bbe2-4efb-8682-14cfc7167e7c https://ipt-obis.gbif.us/resource?r=rcca_transects https://ipt-obis.gbif.us/archive.do?r=rcca_transects 2025-06-03T16:53:10.000Z 2020-12-08T01:07:21.790Z 2026-09-28T07:28:31.521Z event [measurementorfact, occurrence] {'Event': 19375, 'absence': 487162, 'dropped': 0, 'Occurrence': 724871, 'DNADerivedData': 0, 'MeasurementOrFact': 255300} POLYGON((-124.294724 32.694233000000004,-117.264999 32.694233000000004,-117.264999 42.045155,-124.294724 42.045155,-124.294724 32.694233000000004)) ... [{'name': 'U.S. Geological Survey HQ', 'oceanexpert_id': 12976, 'oceanexpert_parent_id': None}, {'name': 'United States Marine Biodiversity Observation Network', 'oceanexpert_id': 23070, 'oceanexpert_parent_id': None}, {'name': 'Central & Northern California Ocean Observing System', 'oceanexpert_id': 23204, 'oceanexpert_parent_id': None}, {'name': 'Reef Check Foundation', 'oceanexpert_id': 232... [{'role': None, 'type': 'creator', 'givenname': 'Jan', 'surname': 'Friewald', 'organization': 'Reef Check Foundation', 'position': 'Executive Director', 'email': 'jfreiwald@reefcheck.org', 'url': None, 'organization_oceanexpert_id': 23205, 'type_display': 'Creator'}, {'role': None, 'type': 'contact', 'givenname': 'Jan', 'surname': 'Friewald', 'organization': 'Reef Check Foundation', 'position'... [{'id': 'b7c47783-a020-4173-b390-7b57c4fa1426', 'name': 'OBIS USA'}] [{'keyword': 'Samplingevent', 'thesaurus': 'GBIF Dataset Type Vocabulary: http://rs.gbif.org/vocabulary/gbif/dataset_type_2015-07-10.xml'}] [{'year': 2026, 'downloads': 3475, 'records': 54337895}, {'year': 2025, 'downloads': 5478, 'records': 553557433}, {'year': 2024, 'downloads': 5514, 'records': 767206545}, {'year': 2023, 'downloads': 3648, 'records': 588494102}, {'year': 2022, 'downloads': 4191, 'records': 426899064}, {'year': 2021, 'downloads': 13776, 'records': 497036713}, {'year': 2020, 'downloads': 747, 'records': 26199396}] [https://goosocean.org/eov/fish, https://goosocean.org/eov/invertebrate, https://goosocean.org/eov/macroalgal, https://ror.org/028paz341, https://rs.obis.org/obis/vocabulary/DatasetType/CitizenScience] None 724871 rcca_transects rcca_transects
2 fbf4cb59-cef3-43be-b0ab-0dbab5f51740 https://ipt-obis.gbif.us/resource?r=marine_ltm_photoplot_surveys https://ipt-obis.gbif.us/archive.do?r=marine_ltm_photoplot_surveys 2025-06-11T19:34:34.000Z 2021-09-28T13:21:46.553Z 2026-09-27T12:12:28.016Z occurrence [measurementorfact] {'Event': 0, 'absence': 148598, 'dropped': 24793, 'Occurrence': 209528, 'DNADerivedData': 0, 'MeasurementOrFact': 628584} POLYGON((-135.37755 33.38499069,-117.71475 33.38499069,-117.71475 57.04869843,-135.37755 57.04869843,-135.37755 33.38499069)) ... [{'name': 'University of California, Marine Science Institute , Santa Barbara', 'oceanexpert_id': 7227, 'oceanexpert_parent_id': None}, {'name': 'University of California, Santa Barbara', 'oceanexpert_id': 16187, 'oceanexpert_parent_id': None}, {'name': 'Univserity of California, Santa Cruz', 'oceanexpert_id': 17545, 'oceanexpert_parent_id': None}, {'name': 'University of California Los Angele... [{'role': None, 'type': 'creator', 'givenname': None, 'surname': None, 'organization': 'Multi-Agency Rocky Intertidal Network (MARINe)', 'position': None, 'email': None, 'url': 'https://pacificrockyintertidal.org', 'organization_oceanexpert_id': None, 'type_display': 'Creator'}, {'role': None, 'type': 'creator', 'givenname': None, 'surname': None, 'organization': 'Partnership for Interdiscipli... [{'id': 'b7c47783-a020-4173-b390-7b57c4fa1426', 'name': 'OBIS USA'}] [{'keyword': 'Ocean', 'thesaurus': 'Global Change Master Directory'}, {'keyword': 'Intertidal Processes', 'thesaurus': 'Global Change Master Directory'}, {'keyword': 'Intertidal Zone', 'thesaurus': 'Global Change Master Directory'}, {'keyword': 'Marine Biology', 'thesaurus': 'Global Change Master Directory'}, {'keyword': 'Marine Invertebrates', 'thesaurus': 'Global Change Master Directory'}, {... [{'year': 2026, 'downloads': 1680, 'records': 7338822}, {'year': 2025, 'downloads': 3436, 'records': 113726768}, {'year': 2024, 'downloads': 3565, 'records': 175165447}, {'year': 2023, 'downloads': 2830, 'records': 130576843}, {'year': 2022, 'downloads': 2621, 'records': 92885821}, {'year': 2021, 'downloads': 1173, 'records': 26122975}] [https://goosocean.org/eov/invertebrate, https://goosocean.org/eov/macroalgal, https://ror.org/028paz341] None 184735 marine_ltm_photoplot_surveys marine_ltm_photoplot_surveys
3 bb638343-bbc9-464d-88d8-18d9ba29ecdd https://ipt-obis.gbif.us/resource?r=marine_cbs_point_contact_surveys https://ipt-obis.gbif.us/archive.do?r=marine_cbs_point_contact_surveys 2025-09-08T16:00:04.000Z 2023-05-02T17:01:15.060Z 2026-09-27T12:21:29.087Z event [measurementorfact, occurrence] {'Event': 429, 'absence': 91528, 'dropped': 1716, 'Occurrence': 111111, 'DNADerivedData': 0, 'MeasurementOrFact': 111111} POLYGON((-136.73148 26.70548058,-113.5763 26.70548058,-113.5763 58.27082062,-136.73148 58.27082062,-136.73148 26.70548058)) ... [{'name': 'Univserity of California, Santa Cruz', 'oceanexpert_id': 17545, 'oceanexpert_parent_id': None}, {'name': 'United States Marine Biodiversity Observation Network', 'oceanexpert_id': 23070, 'oceanexpert_parent_id': None}, {'name': 'Central & Northern California Ocean Observing System', 'oceanexpert_id': 23204, 'oceanexpert_parent_id': None}] [{'role': None, 'type': 'creator', 'givenname': 'Pete', 'surname': 'Raimondi', 'organization': 'Univeristy of California, Santa Cruz', 'position': None, 'email': 'raimondi@ucsc.edu', 'url': None, 'organization_oceanexpert_id': 17545, 'type_display': 'Creator'}, {'role': None, 'type': 'contact', 'givenname': None, 'surname': None, 'organization': 'Multi-Agency Rocky Intertidal Network (MARINe)'... [{'id': 'b7c47783-a020-4173-b390-7b57c4fa1426', 'name': 'OBIS USA'}] [{'keyword': 'Ocean', 'thesaurus': 'Global Change Master Directory'}, {'keyword': 'Intertidal Processes', 'thesaurus': 'Global Change Master Directory'}, {'keyword': 'Intertidal Zone', 'thesaurus': 'Global Change Master Directory'}, {'keyword': 'Marine Biology', 'thesaurus': 'Global Change Master Directory'}, {'keyword': 'Marine Invertebrates', 'thesaurus': 'Global Change Master Directory'}, {... [{'year': 2026, 'downloads': 4296, 'records': 3464087}, {'year': 2025, 'downloads': 5479, 'records': 43235336}, {'year': 2024, 'downloads': 5621, 'records': 62198910}, {'year': 2023, 'downloads': 2444, 'records': 30346241}] [https://goosocean.org/eov/invertebrate, https://goosocean.org/eov/macroalgal, https://ror.org/028paz341] None 109395 marine_cbs_point_contact_surveys marine_cbs_point_contact_surveys
4 d50e0443-4d84-4bd9-a5ad-4d417a7607e2 https://ipt-obis.gbif.us/resource?r=2009floridakeysrvc https://ipt-obis.gbif.us/archive.do?r=2009floridakeysrvc 2025-06-03T16:55:55.000Z NaN 2025-11-12T11:47:36.075Z event [measurementorfact, occurrence] {'Event': 2542, 'absence': 353964, 'dropped': 0, 'Occurrence': 451581, 'DNADerivedData': 0, 'MeasurementOrFact': 905704} POLYGON((-81.9975333 24.4313,-80.08755 24.4313,-80.08755 25.7526,-81.9975333 25.7526,-81.9975333 24.4313)) ... [{'name': 'NOAA, Southeast Fisheries Science Center, National Marine Fisheries Service Southeast Miami Laboratory', 'oceanexpert_id': 7534, 'oceanexpert_parent_id': 11379}, {'name': 'Rosenstiel School of Marine & Atmospheric Science, University of Miami', 'oceanexpert_id': 8124, 'oceanexpert_parent_id': None}, {'name': 'U.S. Geological Survey HQ', 'oceanexpert_id': 12976, 'oceanexpert_parent_i... [{'role': None, 'type': 'creator', 'givenname': 'Jerald', 'surname': 'Ault', 'organization': 'University of Miami, Rosenstiel School of Marine & Atmospheric Science', 'position': 'Program Lead', 'email': 'jault@rsmas.miami.edu', 'url': None, 'organization_oceanexpert_id': 8124, 'type_display': 'Creator'}, {'role': None, 'type': 'creator', 'givenname': 'Jim', 'surname': 'Bohnsack', 'organiz... [{'id': 'b7c47783-a020-4173-b390-7b57c4fa1426', 'name': 'OBIS USA'}] [{'keyword': 'Samplingevent', 'thesaurus': 'GBIF Dataset Type Vocabulary: http://rs.gbif.org/vocabulary/gbif/dataset_type.xml'}, {'keyword': 'Samplingevent', 'thesaurus': 'GBIF Dataset Type Vocabulary: http://rs.gbif.org/vocabulary/gbif/dataset_type_2015-07-10.xml'}] [{'year': 2026, 'downloads': 12848, 'records': 22931680}, {'year': 2025, 'downloads': 17979, 'records': 227140705}, {'year': 2024, 'downloads': 15632, 'records': 317641880}, {'year': 2023, 'downloads': 7679, 'records': 242648438}, {'year': 2022, 'downloads': 7478, 'records': 175113790}, {'year': 2021, 'downloads': 17549, 'records': 204507663}, {'year': 2020, 'downloads': 3443, 'records': 68153... None None 97617 2009floridakeysrvc floridakeysrvc

5 rows × 26 columns

df_meta[["short_name", "short_name_group"]].describe()
short_name short_name_group
count 344 344
unique 344 41
top mlpa_kelp_forest_monitoring atn__ribbon-seal_trajectory_-
freq 1 147

Read to geopandas and as a geojson#

Since we have the data, why don’t we write this out to a geoJson file to interact with on GitHub.

from datetime import datetime

import geopandas

gdf = geopandas.GeoDataFrame(
    df_meta, geometry=geopandas.GeoSeries.from_wkt(df_meta["extent"])
)
gdf.set_crs("epsg:4326", inplace=True)

today = datetime.now().strftime("%Y-%m-%d")
fname = f"US_MBON_bounding_boxes_{today.replace('-','')}.geojson"

# can"t export fields with lists or dictionaries - breaks the format.
gdf[["id", "title", "url", "geometry"]].to_file(fname, driver="GeoJSON")

Query the OBIS aggregated points#

Now that we know the general area for where our observations are made, let’s dig a little deeper into each dataset.

We can use pyobis.occurrences.getpoints() to return all of the points for each dataset, aggregated to Geohash precision 8.

If we’d like a little more metadata about each of the aggregated points, we can query by dataset to collect the aggregated points and some useful metadata.

combined = pd.DataFrame()

query = pyobis.dataset.search(instituteid=institution_id)
df = pd.DataFrame(query.execute())
df_meta = pd.DataFrame.from_records(df["results"])


for datasetid in df_meta["id"]:
    dset = pyobis.occurrences.getpoints(datasetid=datasetid).execute()

    meta = pyobis.dataset.get(id=datasetid).execute()["results"][0]
    short_name = meta["url"].split("=")[-1]

    df = pd.DataFrame(dset)

    df["dataset_id"] = datasetid
    df["short_name"] = meta["url"].split("=")[-1]
    df["short_name_group"] = df["short_name"].replace(r"\d", "", regex=True)
    df["url"] = meta["url"]
    df["metadata"] = str(meta)

    df[["decimalLongitude", "decimalLatitude"]] = pd.DataFrame(
        df["coordinates"].tolist()
    )

    combined = pd.concat([combined, df], ignore_index=True)


gdf_ghsh = geopandas.GeoDataFrame(
    combined[
        ["dataset_id", "url", "short_name_group", "decimalLatitude", "decimalLongitude"]
    ],
    geometry=geopandas.points_from_xy(
        combined.decimalLongitude, combined.decimalLatitude
    ),
    crs="epsg:4326",
)
import cartopy.io.shapereader as shpreader
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(20, 15))

gdf_ghsh.plot(
    ax=ax,
    zorder=10,
    alpha=0.5,
    column="short_name_group",
    legend=True,
)


shpfilename = shpreader.natural_earth(
    resolution="110m",
    category="cultural",
    name="admin_0_countries",
)
world = geopandas.read_file(shpfilename)
world = world.to_crs(gdf.crs)

# bound map to US.
world.loc[world["ISO_A3"] == "USA"].plot(
    ax=ax, color="lightgrey", edgecolor="black", zorder=1
)

now = datetime.now()

today = now.strftime("%Y-%m-%d")

ax.set_title(
    f"Total aggregated occurrence records contributed to OBIS from the US MBON network as of {today}: {gdf_ghsh.shape[0]}"
)

ax.set_axis_off();
../../../_images/acc3cb7d2bd452585c4166eedc8829fa89a1ff45634ee5f7d5f32c5983c6a077.png

It is visualize all those data points and make sense of them. So let’s return all occurences and build a histogram for the data occurrences.


Use pyobis to return all occurrences.#

If you are really interested in all of the records, the code below exemplifies how one might use pyobis to iterate through each dataset and collect only the coordinates to create a map.

Note: This takes a while to run!

First, let’s get all the occurrences as one huge DataFrame.

from pathlib import Path

fname = "combined_high_res.zip"
if Path(fname).exists():
    print(
        f"Loading cached {fname}. Remove this file if you want to fetch all the points again."
    )
    combined_high_res = pd.read_csv(fname, compression="zip")
else:
    combined_high_res = pd.DataFrame()
    query = pyobis.dataset.search(instituteid=institution_id)

    df = pd.DataFrame(query.execute())
    df_meta = pd.DataFrame.from_records(df["results"])

    fields = ["decimalLatitude", "decimalLongitude", "id"]

    for datasetid in df_meta["id"]:
        query = pyobis.occurrences.search(datasetid=datasetid, fields=fields)
        dset = query.execute()

        df = pd.DataFrame(dset)

        combined_high_res = pd.concat([combined_high_res, df], ignore_index=True)

    combined_high_res[["decimalLongitude", "decimalLatitude"]].to_csv(
        fname, index=False, compression="zip"
    )
Loading cached combined_high_res.zip. Remove this file if you want to fetch all the points again.

Next, let’s convert that DataFrame to a GeoDataFrame and plot the points on a map.

gdf_high_res = geopandas.GeoDataFrame(
    combined_high_res[["decimalLatitude", "decimalLongitude"]],
    geometry=geopandas.points_from_xy(
        combined_high_res.decimalLongitude, combined_high_res.decimalLatitude
    ),
    crs="epsg:4326",
)
fig, ax = plt.subplots(figsize=(20, 15))

gdf_high_res.plot.scatter(
    x="decimalLongitude",
    y="decimalLatitude",
    ax=ax,
    s=5,
    zorder=10,
    rasterized=True,
)


world.loc[world["ISO_A3"] == "USA"].plot(
    ax=ax, color="lightgrey", edgecolor="white", zorder=1
)

now = datetime.now()

today = now.strftime("%Y-%m-%d")

ax.set_title(
    f"Total occurrence records contributed to OBIS from the US MBON network as of {today}: {gdf_high_res.shape[0]}"
)

ax.set_axis_off();
../../../_images/2735ec3c8e433f239931c50e5abed5025a6a91554d90d9cd2e9d743de750c9bf.png

Let’s make some histograms of occurrence record distributions across latitude and longitude#

First, we need to create the bins by which we will calculate the number of occurrences. For this example we will use 1 degree bins.

bins_lat = [i for i in range(-90, 90, 1)]
bins_lon = [i for i in range(-180, 180, 1)]

fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(16, 8))

gdf_high_res.hist(
    column="decimalLatitude", bins=bins_lat, orientation="horizontal", ax=ax[0]
)

gdf_high_res.hist(
    column="decimalLongitude", bins=bins_lon, orientation="vertical", ax=ax[1]
)
array([<Axes: title={'center': 'decimalLongitude'}>], dtype=object)
../../../_images/ea2492ba4b35b99c918a1f5cd36f161b73526a2a0aadae190c699089b99b331e.png

Create a grid of observations#

1 degree x 1 degree

import numpy as np
import shapely

# total area for the grid
xmin, ymin, xmax, ymax = [-180.0, -90.0, 180.0, 90]

# how many cells across and down
n_cells = 360

# calculate the size of the cells
cell_size = (xmax - xmin) / n_cells

# create an evenly distributed GeoDataFrame for the grid described above
grid_cells = []
for x0 in np.arange(xmin, xmax + cell_size, cell_size):
    for y0 in np.arange(ymin, ymax + cell_size, cell_size):
        # bounds
        x1 = x0 - cell_size
        y1 = y0 + cell_size
        grid_cells.append(shapely.geometry.box(x0, y0, x1, y1))

cell = geopandas.GeoDataFrame(grid_cells, columns=["geometry"], crs="EPSG:4326")

# spatial join the OBIS data with the one-degree cells for where OBIS data are within cells.
merged = geopandas.sjoin(gdf_high_res, cell, how="left", predicate="within")

# make a simple count variable that we can sum for each cell
merged["n_occur"] = 1

# Compute stats per grid cell -- aggregate occurences to grid cells with dissolve
dissolve = merged.dissolve(by="index_right", aggfunc="count")

# put this into cell
cell.loc[dissolve.index, "n_occur"] = dissolve.n_occur.values

# calculate occurrence density in km^2 for each cell.
cell["area (km2)"] = cell.to_crs(epsg=3763).area / (1000**2)

cell.plot(column="n_occur", legend=True);
/home/filipe/micromamba/envs/IOOS/lib/python3.14/site-packages/shapely/measurement.py:50: RuntimeWarning: invalid value encountered in area
  return lib.area(geometry, **kwargs)
../../../_images/534e3c953cc11a072a56e88ebaf46885d34d62d46641dc470cdb282c546947bc.png

Next, let’s check out the distribution of the values for n_occur so we can make an appropriatly legible map of the distribution.

cell.describe()
/home/filipe/micromamba/envs/IOOS/lib/python3.14/site-packages/pandas/core/nanops.py:1028: RuntimeWarning: invalid value encountered in subtract
  sqr = _ensure_numeric((avg - values) ** 2)
n_occur area (km2)
count 80.000000 6.449600e+04
mean 25000.212500 inf
std 84580.460311 NaN
min 10.000000 1.088584e+02
25% 156.500000 5.065842e+03
50% 594.000000 1.071955e+04
75% 2487.500000 1.742916e+04
max 477661.000000 inf

Since the distribution of n_occur covers a wide range, we will present the concentration and histogram figures as log normal distributions.

So, let’s combine the latitude and longitude histograms with the map to recreate this figure https://bbest.github.io/obis-lat-time-fig.

import geoplot
import geoplot.crs as gcrs
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker

norm = mpl.colors.LogNorm(vmin=cell["n_occur"].min(), vmax=cell["n_occur"].max())

projection = gcrs.PlateCarree(central_longitude=-105)

world = world.dissolve()

fig, ax = plt.subplots(
    figsize=(16, 8), facecolor="white", subplot_kw={"projection": projection}
)

geoplot.polyplot(
    world,
    facecolor="lightgray",
    edgecolor=None,
    projection=projection,
    ax=ax,
)


cax = fig.add_axes([0.225, 0.08, 0.575, 0.03])


cell = cell[~cell["n_occur"].isna()]

geoplot.choropleth(
    cell,
    hue="n_occur",
    edgecolor=None,
    projection=projection,
    ax=ax,
    cmap="Spectral_r",
    legend=True,
    legend_kwargs={
        "orientation": "horizontal",
        "cax": cax,
        "label": "occurrence record log(count)",
    },
    norm=norm,
)

gl = ax.gridlines(draw_labels=True)
gl.ylocator = mticker.FixedLocator(range(0, 90, 5))
gl.xlocator = mticker.FixedLocator(range(-170, -40, 10))


# Add histogram for distribution
ax2 = fig.add_axes([0.95, 0.090, 0.05, 0.8])

bins_lat = [i for i in range(20, 77, 1)]

gdf_high_res.hist(
    column="decimalLatitude", bins=bins_lat, orientation="horizontal", ax=ax2, log=True
)

ax3 = fig.add_axes([0.09, 1, 0.825, 0.08])

bins_lon = [i for i in range(-179, -61, 1)]

gdf_high_res.hist(
    column="decimalLongitude", bins=bins_lon, orientation="vertical", ax=ax3, log=True
);
../../../_images/428008a284db897232fd739b49e4b9c42ea5ba9cac9386baeb111214a6d44a22.png