Chris Parmer — home

Exoplanet scatter

mass against orbital period for 6,000 known planets

Example from Plotly for highly customizable print-ready data visualization · shared helpers

Exoplanet scatter — mass against orbital period for 6,000 known planets

Python Code

"""Exoplanet scatter — mass against orbital period for 6,000 known planets.

A log–log discovery plot in the style of the NASA Exoplanet Archive: points
coloured by detection method, the Solar System planets drawn as labelled
reference marks, and the selection biases of each method visible as the
bands the points fall into.
"""
from pathlib import Path

from _shared import fetch_csv, save, base_layout, titles, INK, MUTED, FAINT, GRID, VIOLET, TEAL, GREEN, PINK, ORANGE, hex_to_rgba

import numpy as np
import plotly.graph_objects as go

CHART_NUM = 13
URL = ("https://exoplanetarchive.ipac.caltech.edu/TAP/sync?query=select+pl_name,discoverymethod,disc_year,"
       "pl_orbper,pl_bmasse,pl_rade+from+pscomppars&format=csv")
METHODS = [("Transit", VIOLET), ("Radial Velocity", TEAL), ("Microlensing", GREEN), ("Imaging", PINK)]
SOLAR = [("Mercury", 88, 0.055), ("Venus", 225, 0.815), ("Earth", 365.25, 1), ("Mars", 687, 0.107),
         ("Jupiter", 4333, 317.8), ("Saturn", 10759, 95.2), ("Uranus", 30687, 14.5), ("Neptune", 60190, 17.1)]


def generate():
    df = fetch_csv(URL).dropna(subset=["pl_orbper", "pl_bmasse"])
    df = df[(df["pl_orbper"] > 0) & (df["pl_bmasse"] > 0)]
    fig = go.Figure()
    named = {m for m, _ in METHODS}
    other = df[~df["discoverymethod"].isin(named)]
    fig.add_trace(go.Scatter(x=other["pl_orbper"], y=other["pl_bmasse"], mode="markers", name=f"Other ({len(other)})",
                             marker=dict(size=5, color=hex_to_rgba(FAINT, 0.7)), text=other["pl_name"],
                             hovertemplate="%{text}<extra>%{customdata}</extra>", customdata=other["discoverymethod"]))
    for method, color in METHODS:
        g = df[df["discoverymethod"] == method]
        fig.add_trace(go.Scatter(x=g["pl_orbper"], y=g["pl_bmasse"], mode="markers", name=f"{method} ({len(g):,})",
                                 marker=dict(size=5, color=hex_to_rgba(color, 0.55), line=dict(width=0)),
                                 text=g["pl_name"], hovertemplate="%{text}<br>P = %{x:.2f} d, M = %{y:.2f} M⊕<extra>" + method + "</extra>"))
    fig.add_trace(go.Scatter(x=[p for _, p, _ in SOLAR], y=[m for _, _, m in SOLAR], mode="markers+text",
                             text=[n for n, _, _ in SOLAR],
                             textposition=["top center", "bottom center", "top right", "bottom center", "top center", "top center", "bottom center", "top center"],
                             textfont=dict(size=12, color=INK), name="Solar System",
                             marker=dict(size=9, color="white", line=dict(color=INK, width=2)), hoverinfo="text"))

    fig.update_layout(**base_layout(margin=dict(l=80, r=40, t=110, b=80), showlegend=True,
                                    legend=dict(x=0.01, y=0.99, xanchor="left", yanchor="top", bgcolor="rgba(255,255,255,0.85)",
                                                font=dict(size=12), itemsizing="constant")))
    fig.update_xaxes(type="log", title_text="Orbital period (days)", tickvals=[0.1, 1, 10, 100, 1000, 1e4, 1e5, 1e6],
                     ticktext=["0.1", "1", "10", "100", "1,000", "10⁴", "10⁵", "10⁶"], range=[-1.2, 6.2])
    fig.update_yaxes(type="log", title_text="Planet mass (Earth masses)", tickvals=[0.1, 1, 10, 100, 1000, 1e4],
                     ticktext=["0.1", "1", "10", "100", "1,000", "10,000"], range=[-1.45, 4.3])
    for m, lab in [(317.8, "Jupiter mass"), (1, "Earth mass")]:
        fig.add_hline(y=m, line=dict(color=GRID, width=1, dash="dot"))
        fig.add_annotation(x=6.1, y=np.log10(m), text=lab, showarrow=False, xanchor="right", yshift=9, font=dict(size=11, color=FAINT))
    fig.add_annotation(x=np.log10(3), y=np.log10(2500), text="Hot Jupiters:<br>massive planets on<br>orbits of a few days", showarrow=True,
                       arrowhead=0, arrowcolor=MUTED, ax=120, ay=-45, font=dict(size=12, color=MUTED), align="left", xanchor="left",
                       bgcolor="rgba(255,255,255,0.85)", borderpad=3)
    fig.add_annotation(x=np.log10(0.11), y=np.log10(0.2), text="Transit surveys reach small planets<br>only on short orbits", showarrow=False,
                       font=dict(size=12, color=MUTED), align="left", xanchor="left", bgcolor="rgba(255,255,255,0.85)", borderpad=3)
    titles(fig, f"{len(df):,} planets around other stars, by how they were found",
           "Minimum mass against orbital period, both on log scales. Each detection method finds a different kind of planet.",
           f"Source: NASA Exoplanet Archive, Planetary Systems Composite Parameters table (queried via TAP; discoveries {df['disc_year'].min():.0f}–{df['disc_year'].max():.0f}).")
    save(CHART_NUM, fig)

Made with Plotly