Observer–Identifier Network — open Shiny app (R) by @andreferrari

Goal of the tool:
To let anyone see and map the network of collaboration behind a project or place on iNaturalist — specifically, who is identifying whose observations. The idea came from wanting to actually visualize the identifiers who help so many of us so much, and how observers and identifiers connect to each other through that work.

Niche it fills in the iNaturalist ecosystem:
iNaturalist doesn’t have a native way to visualize the observer–identifier relationship as a network. There are already great tools aimed at helping people make more identifications faster — jtklein’s quick coarse-ID app being a good example — and this one comes at the same observer/identifier gap from a different angle: instead of helping close it, it helps you see it, by mapping the collaboration that’s already happening around a project, place, species, or specific user.

Is there a commercial component, or do you plan to include one in the future? Are donations requested? (yes/no, explain):
No. The app is free, with no commercial component planned and no donations requested.

What sort of data (if any) does your app collect from its users?:
None. No login is required, and no personal data is collected or stored. The app only reads public iNaturalist API endpoints (observations, identifications, projects, places, taxa) based on the filters you choose.

Link to your iNaturalist profile:

https://www.inaturalist.org/people/andreferrari

Description:
An R Shiny app where you pick any iNaturalist project or place (the field starts empty — you choose your own, nothing is pre-selected) and, optionally, narrow down further by taxonomic group, species, observer, and/or identifier. It then builds an interactive network graph showing who identified whose observations — arrows point from identifier to observer. Click a person (or use the built-in dropdown) to filter a connections table below the graph, showing species, date, location, and a direct link to each observation.

Before running a search, the app shows a live estimate of how many observations match your filters, plus an estimated processing time, and blocks scopes above 100,000 observations to keep it usable for everyone. Available in Portuguese and English.

A couple of practical notes:

  • It’s built for desktop/tablet/notebook use — the graph interaction (dragging nodes, hovering, the layout itself) doesn’t translate well to a phone screen, so mobile isn’t really supported yet.
  • For your first try, I’d suggest starting with a smaller scope — filtering by a single species is a good way to get a feel for it before pulling a whole project or place.

Link: https://andreferrari.shinyapps.io/web_observer_identifier/

Discussion questions or areas seeking feedback:
As with the Ranking/Rarities app, I plan to keep iterating on this based on whatever surfaces here — so please don’t hold back.

  • Is the identifier→observer direction of the arrows intuitive, or would the reverse (or a toggle) make more sense?
  • Any project/place/username combinations that break the search or produce unexpected results?
  • Is the 100,000-observation cap reasonable, or too restrictive/generous for how people might actually want to use this?
  • @jtklein — your quick-ID app and this one are tackling the same observer/identifier gap from opposite ends; curious whether a network view like this would be useful alongside it. @bouteloua, @kiwifergus — tagging you too since the design questions you raised there (what actually helps vs. adds noise) feel very relevant here as well.
  • @enricotosto96 — you’ve been a huge supporter of my own project (Vem Pra Mata) and its top identifier by far, so it felt right to tag you on a tool literally about mapping that kind of contribution. @polones — your BioPoster/TrailCam tool was part of what got me building things for this community in the first place, so thank you, and I’d value your eyes on this too.
  • @matthew_connors, @LostInCR, @ncb1221, @brennafarrell — since you were kind enough to stress-test the Ranking/Rarities app and helped me catch some real bugs there, I’d love it if you had a few minutes to poke at this one too. Thanks again for the help improving the earlier tool!
2 Likes

Good evening @andreferrari

It’s great that I inspired you to create these apps. It’s really nice to be able to meet a need that we have one that not only helps us but also helps the community.

I made a few more tweaks to Inat Trailcam, and now I find it very easy to use.

I’ll check out your new app right away and give you some feedback later.

1 Like

It looks really good! But I guess I’m not entirely sure what it’s supposed to show? If I restrict it to a particular project and/or place, I figured it would show the identifier-observer connections for all the observations in that project and/or place. That does not seem to be the case though. Here is an example from a project I am part of that I restricted just to Australia, which gives very close to the maximum limit that the app can handle (maybe that is part of the problem). It gives a nice network:


But in the image here I have highlighted the user holocene_matt who only has 3 connections. Compare with me in this network - the big node down the bottom, with 746 connections. But this doesn’t seem right at all! Both of us have many thousands of observations in the project, and although holocene_matt does less identification that I do, it is still far more than 3. So what is this network actually showing then?

3 Likes

@matthew_connors — thank you for testing this so carefully. You found a real bug, and a serious one. It is fixed now.

What was happening

The app was paginating with page= and per_page=200. The API stops serving results once page × per_page passes 10,000, and my loop was treating that failed request as “no more data” and stopping quietly. So on any scope larger than 10,000 observations, the network was built from only the first 10,000 records ordered by id — and then displayed as if it were complete.

That matches what you saw. Your Australia scope had around 98,500 observations, but the network only ever knew about a tenth of them, all from one end of the id range. holocene_matt’s identifications appear to have fallen largely outside that window, which would account for the 3 connections. Your own node survived because you had enough activity inside the slice. Either way, the network was not showing what it claimed to show.

What changed

  1. Pagination now uses id_above, which has no ceiling. The slice is gone.
  2. Hard limit is now 10,000 observations per query, with a recommendation of 5,000. In testing after the fix, a scope of roughly 58,000 lost its connection after about 20 minutes of fetching — I have not established whether that came from the API, the hosting platform, or somewhere in between. The cap is set well below that point so that every query the app accepts can actually finish.
  3. The sidebar now states how many observations the network was built from, out of how many exist in the scope, and warns explicitly when a search ended early. No more silent truncation.

One clarification on the original question

The number in the node tooltip is the degree — how many distinct people that node is connected to, not how many identifications it made. Identification counts are carried by the edges. So even with the bug fixed, a prolific identifier who works mostly within a small circle of observers will show a modest number there. Worth knowing when reading the graph.

For scopes larger than 10,000

The fetch has to happen outside the app. One possibility I am evaluating is a standalone collector script that would run on your own machine and export a CSV for the app to load. The iNaturalist observation export cannot be used for this — it does not include identifier usernames, so there is no way to build the edges from it. The data only exists in the API. I have not decided whether to pursue it, but I would be curious to know whether larger scopes are something people here actually need.

Thanks again for the report. This one would have gone unnoticed for a long time.

1 Like