UFO Sightings Comprehensive Analysis and Visualizations

By Eldar · Published July 9, 2026

Exploratory analysis of 80,000+ UFO sightings worldwide covering temporal trends, geospatial patterns, shapes, durations, and seasonal effects with interactive visualizations.

  • eda
  • geospatial
  • time-series
  • data-visualization
  • exploratory-analysis
38 cells1 experiment5 views0 forks

Inside this notebook

# 🛸 UFO Sightings Around the World — Comprehensive Analysis **Dataset:** [UFO Sightings around the World](https://www.kaggle.com/datasets/camnugent/ufo-sightings-around-the-world) (CC0, 80,000+ records) **Source:** Curated from [planetsig/ufo-reports](https://github.com/planetsig/ufo-reports) This notebook performs a thorough exploratory data analysis: temporal trends, geospatial patterns, shape distributions, encounter durations, seasonal effects, and textual description exploration.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import warnings
warnings.filterwarnings('ignore')

# Plot styling
plt.rcParams.update({
    'figure.facecolor': 'white',
    'axes.facecolor': 'white',
    'font.size': 12,
    'axes.titlesize': 14,
    'axes.labelsize': 12,
…
Libraries loaded.
# Load the dataset
df = pd.read_csv('ufo_sighting_data.csv')
print(f"Shape: {df.shape[0]:,} rows × {df.shape[1]} columns")
print(f"Memory usage: {df.memory_usage(deep=True).sum() / 1e6:.1f} MB")
df.head(10)
Shape: 80,332 rows × 11 columns
Memory usage: 57.1 MB
# Schema & data types
dtype_info = pd.DataFrame({
    'Column': df.columns,
    'Dtype': df.dtypes.values,
    'Non-Null': df.notna().sum().values,
    'Null%': (df.isna().sum() / len(df) * 100).round(2).values,
    'Unique': df.nunique().values,
})
print(dtype_info.to_string(index=False))
print(f"\n--- Sample values per column ---")
for col in df.columns:
    print(f"\n{col}: {df[col].dropna().unique()[:5]}")
Column   Dtype  Non-Null  Null%  Unique
                      Date_time  object     80332   0.00   69586
                           city  object     80332   0.00   19900
                 state/province  object     74535   7.22      67
                        country  object     70662  12.04       5
                      UFO_shape  object     78400   2.41      29
    length_of_encounter_seconds  object     80332   0.00     705
described_duration_of_encounter  object     80332   0.00    8304…
# Quick stats and duplicates
print(f"Duplicate rows: {df.duplicated().sum():,}")
print(f"Rows with any null value: {df.isna().any(axis=1).sum():,} ({df.isna().any(axis=1).mean()*100:.1f}%)")

# Column-by-column missing
missing_df = df.isna().sum().to_frame('missing').query('missing > 0').sort_values('missing', ascending=False)
missing_df['pct'] = (missing_df['missing'] / len(df) * 100).round(2)
missing_df
Duplicate rows: 0
Rows with any null value: 13,816 (17.2%)

## Data Cleaning & Preparation

# Convert datetime columns
df['datetime'] = pd.to_datetime(df['Date_time'], format='%m/%d/%Y %H:%M', errors='coerce')
df['date_documented_dt'] = pd.to_datetime(df['date_documented'], format='%m/%d/%Y', errors='coerce')
df['year'] = df['datetime'].dt.year
df['month'] = df['datetime'].dt.month
df['hour'] = df['datetime'].dt.hour
df['day_of_week'] = df['datetime'].dt.dayofweek  # 0=Mon, 6=Sun
df['weekday_name'] = df['datetime'].dt.day_name()

# Convert encounter duration to numeric
df['duration_seconds'] = pd.to_numeric(df['length_of_encounter_seconds'], errors='coerce')
df['latitude'] = pd.to_numeric(df['latitude'], errors='coerce')
df['longitude'] = pd.to_numeric(df['longitude'], errors='coerce')

# Drop rows with invalid datetime (critical for time analysis)
invalid_dt = df['datetime'].isna().sum()
print(f"Invalid datetime parsed: {invalid_dt:,} ({invalid_dt/len(df)*100:.2f}%)")
print(f"Invalid duration parsed: {df['duration_seconds'].isna().sum():,} ({df['duration_seconds'].isna().mean()*100:.2f}%)")
…
Invalid datetime parsed: 694 (0.86%)
Invalid duration parsed: 3 (0.00%)
Date range: 1906-11-11 00:00:00 → 2014-05-08 18:45:00
Year range: 1906 → 2014
Duration range: 0s → 97,836,000s

This is a preview. Open the live notebook to see all 38 cells with their charts and full outputs, or fork it into your own Clusy workspace.

UFO Sightings Comprehensive Analysis and Visualizations | Clusy