Project 1 - UFO Sightings

Image: gallup.com

Project Overview

This project explores patterns in reported UFO shapes and encounter durations. I will identify the three most frequently reported shapes and compare how long their encounters reportedly lasted. These observations describe witness reports and do not establish what the objects actually were.

Research Questions

  1. Which three UFO shapes are reported most frequently?

  2. How do encounter durations compare among these shapes?

Data Overview

The reports originally came from the National UFO Reporting Center and are available through the CORGIS Dataset Project. This file contains 60,632 observations and 16 variables. Each observation represents a reported sighting and includes information about its location, shape, duration, description, and dates. The file also contains U.S. reports with sighting years ranging from 1910 through 2014.

Reported shape is categorical, while encounter duration is quantitative and measured in seconds. Country and state provide categorical location information, and sighting year identifies when the encounter reportedly occurred.

Source: https://corgis-edu.github.io/corgis/csv/ufo_sightings/

1. Loading the Data

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.3     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
ufo <- readr::read_csv("https://corgis-edu.github.io/corgis/datasets/csv/ufo_sightings/ufo_sightings.csv")
Rows: 60632 Columns: 16
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (5): Location.City, Location.State, Location.Country, Data.Shape, Data....
dbl (11): Data.Encounter duration, Location.Coordinates.Latitude, Location.C...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

2. Exploring the Dataset

Before creating visualizations, I check the dataset’s dimensions, column names, and missing values. I also examine country codes and sighting years to confirm the geographic and historical coverage.

# Display the number of observations and variables and the column names.
dim(ufo)
[1] 60632    16
names(ufo)
 [1] "Location.City"                  "Location.State"                
 [3] "Location.Country"               "Data.Shape"                    
 [5] "Data.Encounter duration"        "Data.Description excerpt"      
 [7] "Location.Coordinates.Latitude"  "Location.Coordinates.Longitude"
 [9] "Dates.Sighted.Year"             "Dates.Sighted.Month"           
[11] "Date.Sighted.Day"               "Dates.Sighted.Hour"            
[13] "Dates.Sighted.Minute"           "Dates.Documented.Year"         
[15] "Dates.Documented.Month"         "Dates.Documented.Day"          
# Count missing values in each column to identify potential cleaning needs.
colSums(is.na(ufo))
                 Location.City                 Location.State 
                             0                              0 
              Location.Country                     Data.Shape 
                             0                              0 
       Data.Encounter duration       Data.Description excerpt 
                             0                              0 
 Location.Coordinates.Latitude Location.Coordinates.Longitude 
                             0                              0 
            Dates.Sighted.Year            Dates.Sighted.Month 
                             0                              0 
              Date.Sighted.Day             Dates.Sighted.Hour 
                             0                              0 
          Dates.Sighted.Minute          Dates.Documented.Year 
                             0                              0 
        Dates.Documented.Month           Dates.Documented.Day 
                             0                              0 
# Check which countries appear and summarize the sighting years.
table(ufo$Location.Country)

   US 
60632 
summary(ufo$Dates.Sighted.Year)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1910    2002    2007    2004    2011    2014 

3. Counting the Reported Shapes

I count the reports in each shape category and calculate their proportions of the entire dataset. Sorting the results identifies the most frequently reported shapes.

# Count reports for each shape and calculate its share of all reports.
# Sort the results from the most common shape to the least common.
shape_counts <- ufo %>%
  count(Data.Shape) %>%
  mutate(proportion = n / sum(n)) %>%
  arrange(desc(n))

shape_counts
# A tibble: 28 × 3
   Data.Shape     n proportion
   <chr>      <int>      <dbl>
 1 light      12895     0.213 
 2 triangle    6268     0.103 
 3 circle      5890     0.0971
 4 fireball    4939     0.0815
 5 unknown     4359     0.0719
 6 other       4209     0.0694
 7 sphere      4134     0.0682
 8 disk        3853     0.0635
 9 oval        2881     0.0475
10 formation   1908     0.0315
# ℹ 18 more rows

4. Selecting the Three Most Common Shapes

Light, triangle, and circle are the three most frequently reported categories. Together, they contain 25,053 reports, approximately 41.3% of the dataset. Both visualizations focus on these categories.

# Keep the three most common reported shapes.
top_shapes <- ufo %>%
  filter(Data.Shape %in% c("light", "triangle", "circle"))

# Display the selected reports.
top_shapes
# A tibble: 25,053 × 16
   Location.City Location.State Location.Country Data.Shape
   <chr>         <chr>          <chr>            <chr>     
 1 anchorage     AK             US               circle    
 2 anchorage     AK             US               circle    
 3 anchorage     AK             US               circle    
 4 anchorage     AK             US               circle    
 5 anchorage     AK             US               circle    
 6 anchorage     AK             US               circle    
 7 anchorage     AK             US               circle    
 8 anchorage     AK             US               circle    
 9 anchorage     AK             US               circle    
10 anchorage     AK             US               circle    
# ℹ 25,043 more rows
# ℹ 12 more variables: `Data.Encounter duration` <dbl>,
#   `Data.Description excerpt` <chr>, Location.Coordinates.Latitude <dbl>,
#   Location.Coordinates.Longitude <dbl>, Dates.Sighted.Year <dbl>,
#   Dates.Sighted.Month <dbl>, Date.Sighted.Day <dbl>,
#   Dates.Sighted.Hour <dbl>, Dates.Sighted.Minute <dbl>,
#   Dates.Documented.Year <dbl>, Dates.Documented.Month <dbl>, …

5. Comparing the Number of Reports

A bar chart compares the frequencies of the three selected categories. The colors distinguish the shapes, and the legend identifies each category.

# Count and graph the reports for each selected shape.
ggplot(top_shapes, aes(x = Data.Shape, fill = Data.Shape)) +
  geom_bar() +
  scale_fill_manual(
    values = c(
      circle = "#0072B2",
      light = "#D55E00",
      triangle = "#009E73"
    )
  ) +
  theme_bw() +
  labs(
    title = "Three Most Common Reported UFO Shapes",
    x = "Reported UFO Shape",
    y = "Number of Reported Sightings",
    fill = "Reported Shape",
    caption = "Source: NUFORC reports via CORGIS UFO Sightings CSV"
  )

Light is the most frequently reported category, with 12,895 reports, followed by triangle with 6,268 and circle with 5,890. Light reports are more than twice as common as either triangle or circle reports. The difference between triangle and circle is considerably smaller. These counts describe reports in this collection rather than the actual frequency of unidentified objects.

6. Preparing Encounter Durations

For the duration comparison, I keep positive, nonmissing encounter durations. Positive values are necessary for a logarithmic axis. I retain unusually long encounters so their influence remains visible.

# Keep positive durations that are not missing.
duration_data <- top_shapes %>%
  filter(
    !is.na(`Data.Encounter duration`) &
      `Data.Encounter duration` > 0
  )

# Display the filtered reports.
duration_data
# A tibble: 25,053 × 16
   Location.City Location.State Location.Country Data.Shape
   <chr>         <chr>          <chr>            <chr>     
 1 anchorage     AK             US               circle    
 2 anchorage     AK             US               circle    
 3 anchorage     AK             US               circle    
 4 anchorage     AK             US               circle    
 5 anchorage     AK             US               circle    
 6 anchorage     AK             US               circle    
 7 anchorage     AK             US               circle    
 8 anchorage     AK             US               circle    
 9 anchorage     AK             US               circle    
10 anchorage     AK             US               circle    
# ℹ 25,043 more rows
# ℹ 12 more variables: `Data.Encounter duration` <dbl>,
#   `Data.Description excerpt` <chr>, Location.Coordinates.Latitude <dbl>,
#   Location.Coordinates.Longitude <dbl>, Dates.Sighted.Year <dbl>,
#   Dates.Sighted.Month <dbl>, Date.Sighted.Day <dbl>,
#   Dates.Sighted.Hour <dbl>, Dates.Sighted.Minute <dbl>,
#   Dates.Documented.Year <dbl>, Dates.Documented.Month <dbl>, …

7. Summarizing Encounter Durations

I calculate the number of reports, average duration, and standard deviation for each category. These statistics help describe the differences and variation in reported durations.

# Group reports by shape.
# Calculate the report count, average duration, and standard deviation.
duration_summary <- duration_data %>%
  group_by(Data.Shape) %>%
  summarise(
    reports = n(),
    mean_seconds = mean(`Data.Encounter duration`),
    sd_seconds = sd(`Data.Encounter duration`),
    .groups = "drop"
  )

duration_summary
# A tibble: 3 × 4
  Data.Shape reports mean_seconds sd_seconds
  <chr>        <int>        <dbl>      <dbl>
1 circle        5890        2746.    103616.
2 light        12895       15662.    880035.
3 triangle      6268        1439.     34759.

The average reported duration is approximately 15,662 seconds for light, 1,439 seconds for triangle, and 2,746 seconds for circle. Extremely long reports strongly influence these averages. The large standard deviations also indicate substantial variation within the categories. The box plots provide another way to compare their typical durations.

8. Comparing Encounter Durations

Side-by-side box plots compare encounter durations across the three shapes. A logarithmic axis displays both short and extremely long encounters on the same graph. Equal distances on this axis represent equal ratios rather than equal differences in seconds.

# Compare the duration distributions using a box plot for each shape.
# Use a logarithmic axis because reported durations span a very wide range.
ggplot(
  duration_data,
  aes(x = Data.Shape, y = `Data.Encounter duration`, fill = Data.Shape)
) +
  geom_boxplot() +
  scale_y_log10() +
  scale_fill_manual(
    values = c(
      circle = "#0072B2",
      light = "#D55E00",
      triangle = "#009E73"
    )
  ) +
  theme_bw() +
  labs(
    title = "Reported UFO Encounter Durations by Shape",
    x = "Reported UFO Shape",
    y = "Encounter Duration (seconds; log10 scale)",
    fill = "Reported Shape",
    caption = "Source: NUFORC reports via CORGIS; positive durations only"
  )

The three categories have similar median durations of approximately 180 seconds, or three minutes. Their distributions overlap despite the differences between their averages. Each category contains unusually short and long encounters. Points beyond the whiskers indicate unusual durations under the box plot’s rule, but they are not automatically recording errors.

Conclusion

Light reports are considerably more frequent than triangle or circle reports in this dataset. However, the three categories have similar typical encounter durations, with medians near three minutes. The differences between their averages and medians show how extreme values can influence an average. Examining both frequencies and duration distributions provides a clearer understanding of these reports.

Citations

Data Source: National UFO Reporting Center (NUFORC). UFO sighting reports. Available through the CORGIS Dataset Project. https://corgis-edu.github.io/corgis/csv/ufo_sightings/

R Packages Used: