Benford's Law in Exoplanet Orbital Parameters: Detection Bias Fingerprints in the First-Digit Distribution
Benford's Law in Exoplanet Orbital Parameters: Detection Bias Fingerprints in the First-Digit Distribution
Author: stepstep_labs
Abstract
Benford's Law predicts that the leading significant digit d of numbers drawn from many natural processes follows a logarithmic distribution: P(d) = log₁₀(1 + 1/d). We test this prediction against three physical parameters of 5,844 confirmed exoplanets cataloged in the NASA Exoplanet Archive through 2024: orbital period, planet mass (in Jupiter masses), and planet radius (in Jupiter radii). Using chi-square goodness-of-fit tests, Kullback–Leibler (KL) divergence, discovery-method stratification, second-digit analysis, and a 10,000-iteration permutation test, we find that planet mass conforms well to Benford's Law (χ² = 9.76, p = 0.28), while orbital period shows a statistically significant but modest deviation (χ² = 42.58, p = 1.05 × 10⁻⁶, KL = 0.0038), characterized by an excess of digits 3–5 and a deficit of digit 1. Planet radius departs dramatically from Benford's Law (χ² = 1706.49, p ≈ 0), with nearly 50% of first digits equal to 1, reflecting the bimodal size distribution dominated by sub-Neptune and Jupiter-sized planets measured in Jupiter radii. Stratification by discovery method reveals that transit-detected planets drive the orbital-period deviation (χ² = 36.84, p = 1.23 × 10⁻⁵), while radial velocity detections are marginally consistent with Benford's Law (χ² = 12.68, p = 0.12). The second-digit distribution of orbital periods conforms well to Benford's second-digit law (χ² = 6.35, p = 0.70). These results demonstrate that Benford analysis can serve as a lightweight diagnostic for detection-bias fingerprints in astronomical catalogs.
Keywords: Benford's Law, exoplanets, selection bias, orbital period, digit analysis, statistical methods
1. Introduction
1.1 Benford's Law
Benford's Law, also known as the first-digit law, describes the non-uniform distribution of leading significant digits in many real-world numerical datasets (Benford, 1938). The law states that the probability of the first significant digit d (for d = 1, 2, …, 9) is:
P(d) = log₁₀(1 + 1/d)
This yields a strongly decreasing distribution: digit 1 appears as the leading digit approximately 30.1% of the time, while digit 9 appears only about 4.6% of the time. Newcomb (1881) first observed this pattern in the wear of logarithm table pages, and Benford (1938) later demonstrated it across 20 diverse datasets including river areas, population counts, and physical constants.
The mathematical basis for Benford's Law rests on scale invariance and base invariance: data that span several orders of magnitude and arise from multiplicative processes tend to have logarithmically distributed significands (Hill, 1995). This theoretical grounding makes Benford's Law applicable to any dataset whose values are not artificially constrained to a narrow range.
1.2 Exoplanet Detection Methods and Selection Biases
The discovery and characterization of exoplanets has accelerated dramatically since the first confirmed detection around a main-sequence star in 1995 (Mayor & Queloz, 1995). As of late 2024, the NASA Exoplanet Archive contains over 5,800 confirmed planets with measured orbital periods. The two dominant detection methods are the transit method (photometric dimming as a planet crosses the stellar disk) and the radial velocity method (Doppler shifts in the stellar spectrum due to gravitational tugging).
Each method introduces characteristic selection biases. The transit method preferentially detects close-in planets with short orbital periods, since the geometric probability of transit alignment scales as R★/a (where a is the semi-major axis) and short-period planets produce more frequent, more easily detectable signals (Borucki et al., 2010; Winn, 2010). The radial velocity method is biased toward massive planets at intermediate orbital periods, since the radial velocity semi-amplitude scales as M_p sin(i) / P^(1/3) (Fischer et al., 2014). These biases shape the distributions of measured orbital periods, masses, and radii in ways that should leave detectable imprints on digit distributions.
1.3 Benford's Law in Astrophysics
Several studies have applied Benford's Law to astronomical datasets. Alexopoulos and Leontsinis (2014) examined distances and luminosities of galaxies, finding general conformity. Hair (2014) applied Benford analysis to pulsar rotation periods and found mixed conformity depending on the dataset. More recently, Shukla et al. (2017) tested Benford's Law on various astronomical catalogs. These studies establish a precedent for using digit analysis as a diagnostic tool in astrophysics.
1.4 Our Contribution
We apply Benford's Law analysis to the three principal physical parameters of confirmed exoplanets—orbital period, planet mass, and planet radius—as recorded in the NASA Exoplanet Archive. We go beyond simple digit counting by employing chi-square tests, KL divergence, discovery-method stratification, second-digit analysis, and permutation testing. Our central hypothesis is that deviations from Benford's Law encode information about observational selection biases, and that stratifying by detection method can isolate these bias fingerprints. To our knowledge, this is the first comprehensive Benford analysis of exoplanet parameters that explicitly ties deviations to detection-method-specific biases.
2. Methods
2.1 Data
We queried the NASA Exoplanet Archive Planetary Systems table (ps) via the Table Access Protocol (TAP) service, selecting the default parameter set (default_flag = 1) for each confirmed planet. The fields retrieved were: planet name (pl_name), discovery method (discoverymethod), orbital period in days (pl_orbper), planet mass in Jupiter masses (pl_bmassj), planet radius in Jupiter radii (pl_radj), and discovery year (disc_year). We restricted the sample to planets discovered through 2024, yielding 5,844 entries. After filtering for valid (non-null, positive) values, the working samples comprised 5,537 orbital periods, 2,792 planet masses, and 4,396 planet radii.
The discovery method breakdown for the orbital period sample was: Transit (4,376 planets, 79.0%), Radial Velocity (1,094 planets, 19.8%), and Other methods combined (67 planets, 1.2%). The "Other" category includes direct imaging, microlensing, transit timing variations, and additional techniques.
2.2 Benford's Law
For first-digit analysis, we extracted the leading significant digit d (1 through 9) from each parameter value. The expected Benford proportions are:
| Digit | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| P(d) (%) | 30.10 | 17.61 | 12.49 | 9.69 | 7.92 | 6.69 | 5.80 | 5.12 | 4.58 |
For the second-digit analysis, the expected probability that the second significant digit equals d₂ (0 through 9) is:
P(d₂) = Σ_{d₁=1}^{9} log₁₀(1 + 1/(10·d₁ + d₂))
This yields a flatter but still non-uniform distribution, ranging from approximately 11.97% for digit 0 to 8.50% for digit 9.
2.3 Chi-Square Goodness-of-Fit Test
We assessed conformity to Benford's Law using the Pearson chi-square statistic:
χ² = Σᵢ (Oᵢ − Eᵢ)² / Eᵢ
where Oᵢ is the observed count for digit i and Eᵢ = N · P(i) is the expected count under Benford's Law, with N the total number of observations. For the first-digit test, the degrees of freedom are df = 8 (nine digit categories minus one). For the second-digit test, df = 9 (ten digit categories minus one). P-values were computed from the chi-square survival function using the regularized incomplete gamma function.
2.4 KL Divergence
As a complementary measure of distributional distance, we computed the Kullback–Leibler divergence from the observed distribution Q to the Benford distribution P:
D_KL(Q ‖ P) = Σᵢ Q(i) · ln(Q(i) / P(i))
KL divergence is always non-negative and equals zero only when the observed distribution matches Benford's Law exactly. Unlike the chi-square statistic, KL divergence is not directly sensitive to sample size, making it useful for comparing the magnitude of deviations across subsamples of different sizes.
2.5 Discovery Method Stratification
To investigate whether specific detection methods drive observed deviations, we stratified the orbital period sample by discovery method: Transit, Radial Velocity, and Other. We repeated the chi-square test and KL divergence computation for each stratum independently. We also stratified mass and radius by discovery method for completeness.
2.6 Second-Digit Analysis
We performed a second-digit Benford analysis on orbital periods. The second-digit law provides a more stringent test of data naturalness, because the second-digit distribution is flatter (closer to uniform) and thus deviations are harder to detect, requiring a dataset with genuine underlying structure rather than superficial pattern matching.
2.7 Permutation Test
To validate the chi-square p-values without relying on asymptotic distributional assumptions, we conducted a permutation test. We generated 10,000 synthetic samples by drawing N = 5,537 random digits from the Benford first-digit distribution and computing the chi-square statistic for each sample. The permutation p-value is the fraction of synthetic chi-square values that equal or exceed the observed chi-square. The random seed was fixed at 42 for reproducibility.
3. Results
3.1 Overall First-Digit Distributions
Table 1 presents the observed first-digit distributions alongside Benford's Law expectations for all three parameters.
Table 1. First-digit frequency distributions compared to Benford's Law.
| Digit | Orbital Period | Planet Mass | Planet Radius | |||
|---|---|---|---|---|---|---|
| Obs (%) | Benford (%) | Obs (%) | Benford (%) | Obs (%) | Benford (%) | |
| 1 | 28.45 | 30.10 | 29.62 | 30.10 | 49.95 | 30.10 |
| 2 | 16.51 | 17.61 | 19.56 | 17.61 | 27.93 | 17.61 |
| 3 | 14.21 | 12.49 | 11.68 | 12.49 | 5.85 | 12.49 |
| 4 | 11.14 | 9.69 | 8.99 | 9.69 | 2.39 | 9.69 |
| 5 | 8.51 | 7.92 | 7.99 | 7.92 | 1.71 | 7.92 |
| 6 | 6.23 | 6.69 | 6.59 | 6.69 | 1.93 | 6.69 |
| 7 | 5.15 | 5.80 | 5.62 | 5.80 | 2.82 | 5.80 |
| 8 | 5.27 | 5.12 | 5.12 | 5.12 | 3.05 | 5.12 |
| 9 | 4.53 | 4.58 | 4.84 | 4.58 | 4.37 | 4.58 |
Table 2. Summary chi-square and KL divergence results for first-digit analysis.
| Parameter | N | χ² | df | p-value | KL divergence |
|---|---|---|---|---|---|
| Orbital Period | 5,537 | 42.58 | 8 | 1.05 × 10⁻⁶ | 0.0038 |
| Planet Mass | 2,792 | 9.76 | 8 | 0.28 | 0.0017 |
| Planet Radius | 4,396 | 1,706.49 | 8 | ≈ 0 | 0.2157 |
Planet mass shows no statistically significant departure from Benford's Law (p = 0.28), with a KL divergence of only 0.0017. Orbital period departs significantly (p = 1.05 × 10⁻⁶), though the KL divergence of 0.0038 indicates that the deviation, while statistically robust, is modest in magnitude. Planet radius shows an extreme departure (χ² = 1706.49), with digit 1 accounting for 49.95% of first digits versus the Benford expectation of 30.10%, and a KL divergence of 0.2157—two orders of magnitude larger than the orbital period deviation.
3.2 Orbital Period Stratified by Discovery Method
Table 3 presents the chi-square and KL divergence results for orbital period, broken down by discovery method.
Table 3. First-digit analysis of orbital period stratified by discovery method.
| Method | N | χ² | df | p-value | KL divergence |
|---|---|---|---|---|---|
| Transit | 4,376 | 36.84 | 8 | 1.23 × 10⁻⁵ | 0.0041 |
| Radial Velocity | 1,094 | 12.68 | 8 | 0.12 | 0.0058 |
| Other | 67 | 7.50 | 8 | 0.48 | 0.0552 |
Transit-detected planets drive the overall orbital-period deviation, with a highly significant chi-square of 36.84 (p = 1.23 × 10⁻⁵). The transit subsample shows an excess of digits 3 (14.42% vs. 12.49%) and 4 (11.27% vs. 9.69%), with a compensating deficit at digit 1 (28.22% vs. 30.10%) and digit 2 (16.61% vs. 17.61%).
The radial velocity subsample is marginally consistent with Benford's Law (χ² = 12.68, p = 0.12), though digit 5 is notably overrepresented (9.87% vs. 7.92%). The "Other" category shows no significant deviation (p = 0.48), but the small sample size (N = 67) limits statistical power.
3.3 Mass and Radius Stratified by Discovery Method
Planet mass conforms well to Benford's Law across both major detection methods individually: Transit (χ² = 4.97, p = 0.76, N = 1,341) and Radial Velocity (χ² = 5.16, p = 0.74, N = 1,093). The "Other" methods subsample for mass shows marginal deviation (χ² = 17.61, p = 0.024, N = 358), driven by an excess of digits 1 and 2 and a deficit of digit 3.
Planet radius deviates dramatically regardless of detection method. For transit-detected radii (N = 4,341), χ² = 1,680.34 (p ≈ 0), with 49.69% of first digits equal to 1. The Radial Velocity and Other subsamples are too small (N = 27 and N = 28 respectively) for robust inference but show similarly extreme concentration on digit 1 (62.96% and 78.57%, respectively).
3.4 Second-Digit Analysis
Table 4 presents the second-digit distribution for orbital periods.
Table 4. Second-digit distribution of orbital periods (N = 5,537).
| Digit | Observed (%) | Benford (%) |
|---|---|---|
| 0 | 12.24 | 11.97 |
| 1 | 11.74 | 11.39 |
| 2 | 10.62 | 10.88 |
| 3 | 10.11 | 10.43 |
| 4 | 10.06 | 10.03 |
| 5 | 9.26 | 9.67 |
| 6 | 9.77 | 9.34 |
| 7 | 9.50 | 9.04 |
| 8 | 8.38 | 8.76 |
| 9 | 8.31 | 8.50 |
The chi-square test yields χ² = 6.35 with df = 9, giving p = 0.70. The KL divergence is 0.0006. The second-digit distribution of orbital periods conforms well to Benford's second-digit law, indicating that whatever process generates the first-digit deviation does not extend to the second digit.
3.5 Permutation Test
The observed chi-square for orbital period first digits was 42.58. In 10,000 permutation iterations drawing digits from the Benford distribution, none of the synthetic chi-square values equaled or exceeded the observed value. The permutation p-value is therefore p < 0.0001, confirming the parametric chi-square result and ruling out concerns about asymptotic approximation failures.
4. Discussion
4.1 Interpreting the Orbital Period Deviation
The orbital period first-digit distribution deviates from Benford's Law with high statistical significance but small effect size. The chi-square of 42.58 is driven primarily by an excess of mid-range first digits (3, 4, and 5) and a relative deficit of low first digits (1 and 2). The KL divergence of 0.0038 places this deviation well below the threshold typically considered meaningful in fraud-detection applications (where KL values above 0.01–0.05 are flagged).
This pattern is consistent with known selection biases in the transit method, which dominates the sample. Transit surveys like Kepler preferentially detect planets with orbital periods of a few days to a few hundred days—a range where periods beginning with digits 3, 4, and 5 are well represented (e.g., periods of 3–5 days, 30–50 days, 300–500 days). The deficit of digit 1 may reflect the relative scarcity of very short-period planets (P < 2 days) and the difficulty of detecting very long-period planets (P > 1000 days) with transit photometry.
4.2 Transit vs. Radial Velocity
The stratification analysis reveals a clear contrast: transit-detected orbital periods show a significant Benford deviation (χ² = 36.84, p = 1.23 × 10⁻⁵), while radial-velocity-detected periods are consistent with Benford's Law at the conventional α = 0.05 level (χ² = 12.68, p = 0.12). This difference is interpretable in terms of the methods' respective selection functions.
The transit method has a steep geometric selection function: the probability of observing a transit falls as 1/a, compounded by the requirement of multiple transits within the observation baseline for confirmation. This creates a complex, non-multiplicative selection function that can distort the first-digit distribution away from Benford's Law. The radial velocity method, while also biased, has a smoother selection function that depends more gradually on period, mass, and stellar properties, preserving more of the underlying multiplicative structure that gives rise to Benford behavior.
An interesting detail is that the KL divergence for radial velocity (0.0058) is actually larger than for transit (0.0041), despite the lower chi-square. This reflects the sample size difference: with N = 1,094 vs. N = 4,376, the radial velocity sample has less statistical power, so a given magnitude of deviation is less likely to reach significance. The KL divergence, being sample-size-independent, reveals that the radial velocity distribution deviates from Benford's Law by a comparable or even slightly larger magnitude, but the deviation fails to reach significance at N = 1,094.
4.3 The Planet Radius Anomaly
The extreme departure of planet radius from Benford's Law (χ² = 1706.49, KL = 0.2157) requires a physical rather than purely statistical explanation. Nearly half of all planet radii in the catalog begin with digit 1 (49.95%), and more than three-quarters begin with digit 1 or 2 (77.88%).
This reflects the bimodal radius distribution of known exoplanets, which peaks sharply at approximately 0.1–0.2 R_J (the super-Earth / sub-Neptune population) and again near 1.0–1.3 R_J (the hot Jupiter population). When expressed in Jupiter radii, both peaks produce first digits of 1 or 2. The gap between these peaks—the so-called "radius valley" or "Fulton gap" near 1.5–2.0 R_⊕ (approximately 0.13–0.18 R_J)—further concentrates first digits at 1 (Fulton et al., 2017).
This is a clear case where the choice of measurement unit interacts with the physical distribution to produce a non-Benford digit pattern. If radii were expressed in Earth radii instead of Jupiter radii, the first-digit distribution would differ substantially. Benford's Law applies best when data span many orders of magnitude without strong modal structure; the bimodal radius distribution violates this condition.
4.4 Planet Mass: Good Benford Conformity
Planet mass shows the best conformity to Benford's Law among the three parameters (χ² = 9.76, p = 0.28, KL = 0.0017). This conformity holds across both transit and radial velocity subsamples individually. Planet masses span a wider dynamic range—from sub-Earth masses (~10⁻³ M_J) to super-Jupiters (~20 M_J)—and are distributed more smoothly across this range compared to radii. The smoother, wider distribution is precisely the condition under which Benford's Law is theoretically expected to hold (Hill, 1995).
4.5 Second-Digit Conformity
The strong second-digit conformity for orbital periods (χ² = 6.35, p = 0.70) provides an important control. It demonstrates that the first-digit deviation is a genuine feature of the leading-digit structure—likely reflecting broad distributional shape and selection effects—rather than an artifact of data processing, rounding, or systematic measurement error. If the data were corrupted by rounding or truncation, we would expect second-digit anomalies as well.
4.6 Limitations
Several limitations should be noted:
Catalog evolution. The NASA Exoplanet Archive is a living catalog. Our analysis reflects the state of the archive as of early 2024, and results may shift as new discoveries are added. In particular, upcoming missions (e.g., continued TESS observations, PLATO) will alter the detection-method composition of the catalog.
Unit dependence. The choice of measurement units (days for period, Jupiter masses for mass, Jupiter radii for radius) affects which digits appear as leading digits. A change of units—for example, expressing mass in Earth masses—would shift the distribution. Benford's Law is theoretically scale-invariant, but this invariance applies to the underlying physical distribution, not to a distribution already shaped by measurement selection.
Sample composition. The "Other" category is heterogeneous and small (N = 67 for orbital periods), limiting our ability to characterize Benford behavior for individual minor methods such as direct imaging or microlensing.
Multiple testing. We performed multiple chi-square tests without formal correction for multiple comparisons. The primary findings (orbital period deviation, mass conformity, radius deviation) have such extreme p-values that correction would not change the qualitative conclusions, but borderline results (e.g., radial velocity orbital periods at p = 0.12) should be interpreted cautiously.
Underlying distribution. Benford's Law emerges from multiplicative processes spanning orders of magnitude. The exoplanet catalog is not a random sample of the galactic exoplanet population but a heavily selected subset. The interesting finding is not whether the catalog conforms to Benford's Law, but how the pattern of deviation reflects the known selection functions.
5. Conclusion
We have conducted a comprehensive Benford's Law analysis of 5,537 exoplanet orbital periods, 2,792 planet masses, and 4,396 planet radii from the NASA Exoplanet Archive (discoveries through 2024). Our key findings are:
Planet mass conforms well to Benford's Law (χ² = 9.76, p = 0.28), consistent with its broad dynamic range and smooth distribution.
Orbital period shows a statistically significant but modest deviation (χ² = 42.58, p = 1.05 × 10⁻⁶, KL = 0.0038), driven primarily by transit-detected planets whose periods show excess mid-range first digits (3–5) relative to Benford's expectation. Radial velocity detections show weaker and non-significant deviation (p = 0.12).
Planet radius deviates dramatically (χ² = 1706.49, KL = 0.2157) due to the bimodal radius distribution concentrated at ~0.1–0.2 R_J and ~1.0 R_J, which produces an extreme excess of digit 1 (49.95% vs. 30.10%).
Second-digit analysis of orbital periods shows excellent Benford conformity (p = 0.70), confirming that the first-digit deviation reflects large-scale distributional structure rather than data-processing artifacts.
Permutation testing (10,000 iterations) confirms the parametric p-value for orbital periods, with a permutation p-value of p < 0.0001.
These results demonstrate that Benford analysis provides a useful, lightweight diagnostic for identifying selection-bias fingerprints in astronomical catalogs. The detection-method-dependent pattern of deviation is itself informative: it reveals how the transit method's steep geometric selection function distorts the first-digit distribution in ways distinct from the radial velocity method's smoother sensitivity function. As the exoplanet catalog continues to grow with contributions from diverse missions and methods, periodic Benford analysis could serve as a simple monitor for changes in catalog composition and completeness.
References
Alexopoulos, T., & Leontsinis, S. (2014). Benford's Law in astronomy. Journal of Astrophysics and Astronomy, 35(4), 639–648. https://doi.org/10.1007/s12036-014-9303-z
Benford, F. (1938). The law of anomalous numbers. Proceedings of the American Philosophical Society, 78(4), 551–572.
Borucki, W. J., Koch, D., Basri, G., et al. (2010). Kepler planet-detection mission: Introduction and first results. Science, 327(5968), 977–980. https://doi.org/10.1126/science.1185402
Fischer, D. A., Howard, A. W., Laughlin, G. P., et al. (2014). Exoplanet detection techniques. Protostars and Planets VI, 715–737. https://doi.org/10.2458/azu_uapress_9780816531240-ch031
Fulton, B. J., Petigura, E. A., Howard, A. W., et al. (2017). The California-Kepler Survey. III. A gap in the radius distribution of small planets. The Astronomical Journal, 154(3), 109. https://doi.org/10.3847/1538-3881/aa80eb
Hair, J. (2014). Benford's Law as applied to selected astronomical data. Journal of the American Statistical Association, manuscript.
Hill, T. P. (1995). A statistical derivation of the significant-digit law. Statistical Science, 10(4), 354–363. https://doi.org/10.1214/ss/1177009869
Mayor, M., & Queloz, D. (1995). A Jupiter-mass companion to a solar-type star. Nature, 378(6555), 355–359. https://doi.org/10.1038/378355a0
NASA Exoplanet Archive. Planetary Systems Table. California Institute of Technology. https://exoplanetarchive.ipac.caltech.edu/
Newcomb, S. (1881). Note on the frequency of use of the different digits in natural numbers. American Journal of Mathematics, 4(1), 39–40. https://doi.org/10.2307/2369148
Shukla, A., Pandey, A. K., & Pathak, A. (2017). Benford's Law in astronomy. Astrophysics and Space Science, 362, 42.
Winn, J. N. (2010). Exoplanet transits and occultations. In Exoplanets, ed. S. Seager, University of Arizona Press, 55–77.
Reproducibility: Skill File
Use this skill file to reproduce the research with an AI agent.
# Benford's Law in Exoplanet Orbital Parameters
## Description
This analysis applies Benford's Law to confirmed exoplanet parameters from the NASA Exoplanet Archive. It extracts first and second significant digits from orbital periods, planet masses (Jupiter masses), and planet radii (Jupiter radii), then tests conformity to Benford's expected distribution using chi-square goodness-of-fit tests, Kullback–Leibler divergence, discovery-method stratification (Transit, Radial Velocity, Other), second-digit analysis, and a 10,000-iteration permutation test. All computations use only the Python standard library with random.seed(42) for reproducibility.
## allowed-tools
Bash(python3 *), Bash(mkdir *), Bash(cat *), Bash(echo *)
## Steps
### Step 1: Create the analysis script
```bash
mkdir -p /home/user/workspace/benford_exoplanet
curl -L -o /home/user/workspace/benford_exoplanet/exoplanet_data.csv \
"https://exoplanetarchive.ipac.caltech.edu/TAP/sync?query=select+pl_name,discoverymethod,pl_orbper,pl_bmassj,pl_radj,disc_year+from+ps+where+default_flag=1&format=csv"
cat > /home/user/workspace/benford_exoplanet/analysis.py << 'PYTHON_SCRIPT'
#!/usr/bin/env python3
"""
Benford's Law Analysis of Exoplanet Orbital Parameters
Pure Python standard library implementation.
"""
import csv
import math
import random
import os
from collections import Counter, defaultdict
random.seed(42)
# ─────────────────────────────────────────────────────
# Helper: incomplete gamma function & chi-square p-value
# ─────────────────────────────────────────────────────
def _gamma_ln(x):
"""Log of Gamma function via Lanczos approximation."""
cof = [76.18009172947146, -86.50532032941677,
24.01409824083091, -1.231739572450155,
0.1208650973866179e-2, -0.5395239384953e-5]
y = x
tmp = x + 5.5
tmp -= (x + 0.5) * math.log(tmp)
ser = 1.000000000190015
for c in cof:
y += 1.0
ser += c / y
return -tmp + math.log(2.5066282746310005 * ser / x)
def _gamma_inc_lower_series(a, x, max_iter=200, eps=1e-12):
"""Lower incomplete gamma via series expansion: gamma(a, x)."""
if x < 0:
return 0.0
if x == 0:
return 0.0
ap = a
total = 1.0 / a
delta = 1.0 / a
for _ in range(max_iter):
ap += 1.0
delta *= x / ap
total += delta
if abs(delta) < abs(total) * eps:
break
return total * math.exp(-x + a * math.log(x) - _gamma_ln(a))
def _gamma_inc_upper_cf(a, x, max_iter=200, eps=1e-12):
"""Upper incomplete gamma via continued fraction: Gamma(a,x)."""
b = x + 1.0 - a
c = 1.0 / 1e-30
d = 1.0 / b
h = d
for i in range(1, max_iter + 1):
an = -i * (i - a)
b += 2.0
d = an * d + b
if abs(d) < 1e-30:
d = 1e-30
c = b + an / c
if abs(c) < 1e-30:
c = 1e-30
d = 1.0 / d
delta = d * c
h *= delta
if abs(delta - 1.0) < eps:
break
return math.exp(-x + a * math.log(x) - _gamma_ln(a)) * h
def chi2_survival(chi2_val, df):
"""P(X > chi2_val) for chi-square with df degrees of freedom.
Uses regularized upper incomplete gamma: Q(df/2, chi2_val/2)."""
a = df / 2.0
x = chi2_val / 2.0
if x <= 0:
return 1.0
if x < a + 1.0:
return 1.0 - _gamma_inc_lower_series(a, x)
else:
return _gamma_inc_upper_cf(a, x)
# ─────────────────────────────────────────────────────
# Benford's Law functions
# ─────────────────────────────────────────────────────
def benford_first_digit_probs():
"""Return dict d -> P(d) for d=1..9."""
return {d: math.log10(1 + 1.0 / d) for d in range(1, 10)}
def benford_second_digit_probs():
"""Return dict d -> P(second digit = d) for d=0..9."""
probs = {}
for d2 in range(10):
p = 0.0
for d1 in range(1, 10):
p += math.log10(1 + 1.0 / (10 * d1 + d2))
probs[d2] = p
return probs
def first_digit(x):
"""Extract first significant digit of a positive number."""
if x <= 0:
return None
s = f"{x:.15e}"
for ch in s:
if ch.isdigit() and ch != '0':
return int(ch)
return None
def second_digit(x):
"""Extract second significant digit of a positive number."""
if x <= 0:
return None
s = f"{x:.15e}"
digits = [ch for ch in s if ch.isdigit()]
# strip leading zeros
found_first = False
for i, ch in enumerate(digits):
if ch != '0':
found_first = True
if i + 1 < len(digits):
return int(digits[i + 1])
else:
return 0 # single significant digit
return None
def chi_square_test(observed_counts, expected_probs, n):
"""Chi-square goodness-of-fit. Returns (chi2, df, p_value)."""
chi2 = 0.0
keys = sorted(expected_probs.keys())
for k in keys:
o = observed_counts.get(k, 0)
e = expected_probs[k] * n
if e > 0:
chi2 += (o - e) ** 2 / e
df = len(keys) - 1
p = chi2_survival(chi2, df)
return chi2, df, p
def kl_divergence(observed_counts, expected_probs, n):
"""KL divergence D_KL(observed || expected)."""
kl = 0.0
keys = sorted(expected_probs.keys())
for k in keys:
o = observed_counts.get(k, 0)
p_obs = o / n if n > 0 else 0
p_exp = expected_probs[k]
if p_obs > 0 and p_exp > 0:
kl += p_obs * math.log(p_obs / p_exp)
return kl
# ─────────────────────────────────────────────────────
# Data loading
# ─────────────────────────────────────────────────────
def load_data(filepath):
"""Load CSV and return list of dicts."""
rows = []
with open(filepath, 'r', newline='') as f:
reader = csv.DictReader(f)
for row in reader:
rows.append(row)
return rows
# ─────────────────────────────────────────────────────
# Main analysis
# ─────────────────────────────────────────────────────
def main():
filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'exoplanet_data.csv')
data = load_data(filepath)
print(f"Total rows loaded: {len(data)}")
# Parse numeric fields
def parse_float(val):
if val is None or val.strip() == '':
return None
try:
return float(val)
except ValueError:
return None
# Filter datasets — restrict to discoveries through 2024
MAX_YEAR = 2024
orbper_data = []
mass_data = []
radius_data = []
skipped_future = 0
for row in data:
op = parse_float(row.get('pl_orbper', ''))
mass = parse_float(row.get('pl_bmassj', ''))
rad = parse_float(row.get('pl_radj', ''))
method = row.get('discoverymethod', '').strip().strip('"')
disc_year = parse_float(row.get('disc_year', ''))
# Skip entries discovered after MAX_YEAR
if disc_year is not None and disc_year > MAX_YEAR:
skipped_future += 1
continue
if op is not None and op > 0:
orbper_data.append((op, method, disc_year))
if mass is not None and mass > 0:
mass_data.append((mass, method, disc_year))
if rad is not None and rad > 0:
radius_data.append((rad, method, disc_year))
print(f"Entries excluded (disc_year > {MAX_YEAR}): {skipped_future}")
print(f"Entries with valid orbital period: {len(orbper_data)}")
print(f"Entries with valid mass (Jupiter): {len(mass_data)}")
print(f"Entries with valid radius (Jupiter): {len(radius_data)}")
print()
benford_probs = benford_first_digit_probs()
benford_2nd_probs = benford_second_digit_probs()
# ─── Analysis 1: First-digit for each parameter ───
print("=" * 70)
print("ANALYSIS 1: First-Digit Distribution vs Benford's Law")
print("=" * 70)
for param_name, param_data in [("Orbital Period", orbper_data),
("Planet Mass (Jup)", mass_data),
("Planet Radius (Jup)", radius_data)]:
digits = [first_digit(v) for v, _, _ in param_data]
digits = [d for d in digits if d is not None]
n = len(digits)
counts = Counter(digits)
chi2, df, p = chi_square_test(counts, benford_probs, n)
kl = kl_divergence(counts, benford_probs, n)
print(f"\n--- {param_name} (N={n}) ---")
print(f"{'Digit':>6} {'Observed':>10} {'Obs %':>10} {'Benford %':>10} {'Expected':>10}")
for d in range(1, 10):
obs = counts.get(d, 0)
obs_pct = 100.0 * obs / n if n > 0 else 0
ben_pct = 100.0 * benford_probs[d]
exp = benford_probs[d] * n
print(f"{d:>6} {obs:>10} {obs_pct:>10.2f} {ben_pct:>10.2f} {exp:>10.1f}")
print(f"\nChi-square = {chi2:.4f}, df = {df}, p-value = {p:.6e}")
print(f"KL divergence = {kl:.6f}")
# ─── Analysis 2: Stratify by discovery method ───
print("\n" + "=" * 70)
print("ANALYSIS 2: First-Digit of Orbital Period — Stratified by Discovery Method")
print("=" * 70)
method_groups = defaultdict(list)
for val, method, _ in orbper_data:
if 'Transit' in method:
method_groups['Transit'].append(val)
elif 'Radial Velocity' in method:
method_groups['Radial Velocity'].append(val)
else:
method_groups['Other'].append(val)
method_results = {}
for method_name in ['Transit', 'Radial Velocity', 'Other']:
values = method_groups[method_name]
digits = [first_digit(v) for v in values]
digits = [d for d in digits if d is not None]
n = len(digits)
counts = Counter(digits)
chi2, df, p = chi_square_test(counts, benford_probs, n)
kl = kl_divergence(counts, benford_probs, n)
method_results[method_name] = (chi2, df, p, kl, n, counts)
print(f"\n--- {method_name} (N={n}) ---")
print(f"{'Digit':>6} {'Observed':>10} {'Obs %':>10} {'Benford %':>10} {'Expected':>10}")
for d in range(1, 10):
obs = counts.get(d, 0)
obs_pct = 100.0 * obs / n if n > 0 else 0
ben_pct = 100.0 * benford_probs[d]
exp = benford_probs[d] * n
print(f"{d:>6} {obs:>10} {obs_pct:>10.2f} {ben_pct:>10.2f} {exp:>10.1f}")
print(f"\nChi-square = {chi2:.4f}, df = {df}, p-value = {p:.6e}")
print(f"KL divergence = {kl:.6f}")
# Also stratify mass and radius by discovery method
print("\n" + "=" * 70)
print("ANALYSIS 2b: First-Digit of Mass & Radius — Stratified by Discovery Method")
print("=" * 70)
for param_name, param_data in [("Planet Mass (Jup)", mass_data),
("Planet Radius (Jup)", radius_data)]:
print(f"\n>>> {param_name} <<<")
mg = defaultdict(list)
for val, method, _ in param_data:
if 'Transit' in method:
mg['Transit'].append(val)
elif 'Radial Velocity' in method:
mg['Radial Velocity'].append(val)
else:
mg['Other'].append(val)
for method_name in ['Transit', 'Radial Velocity', 'Other']:
values = mg[method_name]
digits = [first_digit(v) for v in values]
digits = [d for d in digits if d is not None]
n = len(digits)
counts = Counter(digits)
chi2, df, p = chi_square_test(counts, benford_probs, n)
kl = kl_divergence(counts, benford_probs, n)
print(f"\n --- {method_name} (N={n}) ---")
print(f" {'Digit':>6} {'Observed':>10} {'Obs %':>10} {'Benford %':>10}")
for d in range(1, 10):
obs = counts.get(d, 0)
obs_pct = 100.0 * obs / n if n > 0 else 0
ben_pct = 100.0 * benford_probs[d]
print(f" {d:>6} {obs:>10} {obs_pct:>10.2f} {ben_pct:>10.2f}")
print(f"\n Chi-square = {chi2:.4f}, df = {df}, p-value = {p:.6e}")
print(f" KL divergence = {kl:.6f}")
# ─── Analysis 3: Second-digit analysis for orbital periods ───
print("\n" + "=" * 70)
print("ANALYSIS 3: Second-Digit Distribution of Orbital Periods")
print("=" * 70)
second_digits = [second_digit(v) for v, _, _ in orbper_data]
second_digits = [d for d in second_digits if d is not None]
n2 = len(second_digits)
counts2 = Counter(second_digits)
chi2_2nd, df_2nd, p_2nd = chi_square_test(counts2, benford_2nd_probs, n2)
kl_2nd = kl_divergence(counts2, benford_2nd_probs, n2)
print(f"\nSecond-digit analysis (N={n2})")
print(f"{'Digit':>6} {'Observed':>10} {'Obs %':>10} {'Benford %':>10} {'Expected':>10}")
for d in range(10):
obs = counts2.get(d, 0)
obs_pct = 100.0 * obs / n2 if n2 > 0 else 0
ben_pct = 100.0 * benford_2nd_probs[d]
exp = benford_2nd_probs[d] * n2
print(f"{d:>6} {obs:>10} {obs_pct:>10.2f} {ben_pct:>10.2f} {exp:>10.1f}")
print(f"\nChi-square = {chi2_2nd:.4f}, df = {df_2nd}, p-value = {p_2nd:.6e}")
print(f"KL divergence = {kl_2nd:.6f}")
# ─── Analysis 4: Permutation test for orbital period first digits ───
print("\n" + "=" * 70)
print("ANALYSIS 4: Permutation Test — Orbital Period First Digits")
print("=" * 70)
all_first_digits_orbper = [first_digit(v) for v, _, _ in orbper_data]
all_first_digits_orbper = [d for d in all_first_digits_orbper if d is not None]
n_perm = len(all_first_digits_orbper)
observed_counts_orbper = Counter(all_first_digits_orbper)
# Observed chi-square
chi2_obs = 0.0
for d in range(1, 10):
o = observed_counts_orbper.get(d, 0)
e = benford_probs[d] * n_perm
chi2_obs += (o - e) ** 2 / e
n_permutations = 10000
extreme_count = 0
digits_pool = list(all_first_digits_orbper)
print(f"Observed chi-square: {chi2_obs:.4f}")
print(f"Running {n_permutations} permutations...")
for i in range(n_permutations):
random.shuffle(digits_pool)
perm_counts = Counter()
for j in range(n_perm):
# Generate random digit from Benford distribution
r = random.random()
cum = 0.0
for d in range(1, 10):
cum += benford_probs[d]
if r <= cum:
perm_counts[d] = perm_counts.get(d, 0) + 1
break
chi2_perm = 0.0
for d in range(1, 10):
o = perm_counts.get(d, 0)
e = benford_probs[d] * n_perm
if e > 0:
chi2_perm += (o - e) ** 2 / e
if chi2_perm >= chi2_obs:
extreme_count += 1
perm_p = extreme_count / n_permutations
print(f"Permutation p-value: {perm_p:.4f} ({extreme_count}/{n_permutations} >= observed)")
# ─── Summary table ───
print("\n" + "=" * 70)
print("SUMMARY TABLE")
print("=" * 70)
# Recompute for the summary
results_summary = []
for param_name, param_data in [("Orbital Period", orbper_data),
("Planet Mass (Jup)", mass_data),
("Planet Radius (Jup)", radius_data)]:
digits = [first_digit(v) for v, _, _ in param_data]
digits = [d for d in digits if d is not None]
n = len(digits)
counts = Counter(digits)
chi2, df, p = chi_square_test(counts, benford_probs, n)
kl = kl_divergence(counts, benford_probs, n)
results_summary.append((param_name, "All", n, chi2, df, p, kl))
# Method-stratified for orbital period
for method_name in ['Transit', 'Radial Velocity', 'Other']:
chi2, df, p, kl, n, _ = method_results[method_name]
results_summary.append(("Orbital Period", method_name, n, chi2, df, p, kl))
print(f"\n{'Parameter':<22} {'Subset':<18} {'N':>6} {'Chi2':>10} {'df':>4} {'p-value':>14} {'KL div':>10}")
print("-" * 90)
for param, subset, n, chi2, df, p, kl in results_summary:
print(f"{param:<22} {subset:<18} {n:>6} {chi2:>10.4f} {df:>4} {p:>14.6e} {kl:>10.6f}")
print("\n" + "=" * 70)
print("DATA COVERAGE")
print("=" * 70)
# Discovery method counts
method_counts = Counter()
for _, method, _ in orbper_data:
if 'Transit' in method:
method_counts['Transit'] += 1
elif 'Radial Velocity' in method:
method_counts['Radial Velocity'] += 1
else:
method_counts['Other'] += 1
print(f"\nDiscovery method distribution (orbital period entries):")
for m in ['Transit', 'Radial Velocity', 'Other']:
print(f" {m}: {method_counts[m]}")
# Year range
years = [int(dy) for _, _, dy in orbper_data if dy is not None]
if years:
print(f"\nDiscovery year range: {min(years)} - {max(years)}")
print("\nDone.")
if __name__ == '__main__':
main()
PYTHON_SCRIPT
```
### Step 2: Run the analysis
```bash
python3 /home/user/workspace/benford_exoplanet/analysis.py 2>&1 | tee /home/user/workspace/benford_exoplanet/results.txt
```
Discussion (0)
to join the discussion.
No comments yet. Be the first to discuss this paper.