Migrant deaths recorded on the Arizona-Mexico border
Author
Brad Jones (bsjjones@ucdavis.edu)
Published
August 1, 2026
Toplines
This analysis makes use of the migrant death data reported on the Humane Borders website (https://www.humaneborders.org/). These are freely available data and the R code generating this analysis is shown in the file (I do not claim to be a pristine coder, but the code works). The following are the major toplines:
The migrant death crisis emerges in the early 2000s and has been persistent since then, with obvious yearly variation.
The vast majority of remains recovered are males, although the number of female remains has increased in recent years.
The typical age range of migrant deaths is 20 to 34 years of age. Over 1/3rd of remains recovered upon which age can be determined fall within this range.
There are substantially large numbers of remains for which gender, age, and cause of death cannot be determined.
About 34% of deaths are due to exposure. About 35% of remains recovered are skeletal.
About 23% of remains recovered are of migrants who likely died more than 6-8 months before they were recovered (which could imply the remains may be many years old). I call these “noncontemporaneous deaths.” Most remains recovered over the time frame are of migrants estimated to have died within 6-8 months of recovery. I call these “contemporaneous deaths.” The number of noncontemporaneous deaths has increased overtime, implying that in more recent years, many remains recovered are of deaths that occurred at least 6-8 months earlier.
However, in every year, except for 2025, the number of contemporaneous deaths exceeds noncontemporaneous deaths, implying the death crisis is ongoing.
Understanding the migrant death issue
The data on migrant deaths comes from the Arizona OpenGIS Project, cosponsored by Humane Borders and the Pima County Office of Medical Examiner. I’ve edited the data set to include all recorded migrant deaths between 2000 up through July of 2026. My goal with any analysis of these data is to always honor and humanize those who have died in the desert. These data permit a better understanding of the death crisis.
A note about the data
The data on migrant deaths comes from the Arizona OpenGIS Project, cosponsored by Humane Borders and the Pima County Office of Medical Examiner. Recorded remains are between the years 1981 to 2026. However systematic data on migrant deaths only are found from 2000 and later. This is primarily because the death crisis really doesn’t emerge until around the year 2000.
The reason the crisis was nonexistent before 2000 is that very, very few people crossed through Arizona. This changed due to changes made in the Clinton Administration that led to the funnel effect pushing migrants into the Arizona corridor. So if one downloads the death map data, one will find that 4,505 migrant remains have been recovered (as of 7/31/2026); however, 4,371 have been recovered since the year 2000. This means that in the death map, only 134 remains are recorded as being found between 1981 and 1999 (an 18 year period). I don’t mean “only” in a demeaning way; rather, relative to the mass death crisis, the total number of deaths reported in the 18 year period between 1981 to 1999 is far lower than the average yearly number of deaths after the year 2000.
The data file is a csv file saved to my GitHub site. I dynamically update the data as new information is added. This file is current through 7/31/26.
Rows: 4505 Columns: 21
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (13): ML Number, Name, Sex, Surface Management, Location, Location Prec...
dbl (7): Age, Corridor Code, Condition Code, Latitude, Longitude, UTM X, U...
date (1): Reporting Date
ℹ 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.
md <- md
Migrant deaths by year and decade
Below, I create a table of the total number of recovered remains by calendar year. The percentage in the table corresponds to the “contribution” in percentage terms each year makes to the total. In other words, consider the year 2002. In that year, the table shows that 151 remains were recovered.
Of the total number of remains reported between 1981 to July 31, 2026, 2002 contributed 3.35% of the total number of deaths (i.e. total reported deaths is 4,505 and the total deaths in 2002 was 151; the percentage contribution is thus \(\frac{151}{4505}\times 100 \approx 3.35\%\)).
Observe that years before 2000, there were very few recovered remains; after 2000, the death crisis emerges. Note that the years 2020 and 2021—years enmeshed in the COVID pandemic—mark two of the three highest recovery years in the times series. I will have more to say on this below.
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
md$year
n
percent
1981
1
0.02%
1982
1
0.02%
1985
3
0.07%
1987
1
0.02%
1990
9
0.20%
1991
6
0.13%
1992
7
0.16%
1993
17
0.38%
1994
4
0.09%
1995
12
0.27%
1996
13
0.29%
1997
22
0.49%
1998
15
0.33%
1999
23
0.51%
2000
75
1.66%
2001
79
1.75%
2002
151
3.35%
2003
164
3.64%
2004
186
4.13%
2005
203
4.51%
2006
174
3.86%
2007
221
4.91%
2008
166
3.68%
2009
197
4.37%
2010
224
4.97%
2011
182
4.04%
2012
163
3.62%
2013
184
4.08%
2014
140
3.11%
2015
147
3.26%
2016
164
3.64%
2017
127
2.82%
2018
129
2.86%
2019
146
3.24%
2020
212
4.71%
2021
229
5.08%
2022
174
3.86%
2023
200
4.44%
2024
158
3.51%
2025
103
2.29%
2026
73
1.62%
Total
4505
-
Visualizing migrant deaths by year
Below, I produce a bar plot of migrant deaths by year. Each bar corresponds to the number of migrant remains recovered in each year. As is clear from the plot, the death crisis emerges in 2000 and is persistent over time.
yearplot<-ggplot(md, aes(factor(year))) +geom_bar(fill ="lightskyblue4") +labs(title="Migrant remains recovered by year along the Arizona/Mexico border, \n1981- July, 2026", subtitle="Data from the Arizon OpenGIS Project (https://humaneborders.info/app/map.asp)",y="Number recovered", x="Year") +theme_classic()yearplot<- yearplot +theme(axis.text.x =element_text(angle =90, vjust =0.5, hjust=1))yearplot
Understanding gender and migrant deaths
The overwhelming number of migrant deaths are male. The analysis below shows this clearly; about 81% of the total remains recovered are male. Since gender is not always determined, there is a category called “undetermined.”
md$gender n percent valid_percent
male 3630 0.8057713651 0.80630831
female 643 0.1427302997 0.14282541
undetermined 229 0.0508324084 0.05086628
<NA> 3 0.0006659267 NA
There are no discernible trends in gender differences. There has been some speculation that if asylum seeker deaths begin to rise, then more females will die given a large share of asylum seekers are women. Here is a link to a fairly recent article on this: https://19thnews.org/2024/07/women-migrants-deaths-us-mexico-border/
Below is a frequency table of remains recovered by year and gender.
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
gendertable
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
year/gender
male
female
undetermined
NA_
1981
0% (0)
0% (0)
100% (1)
0% (0)
1982
0% (0)
100% (1)
0% (0)
0% (0)
1985
100% (3)
0% (0)
0% (0)
0% (0)
1987
100% (1)
0% (0)
0% (0)
0% (0)
1990
56% (5)
0% (0)
44% (4)
0% (0)
1991
67% (4)
0% (0)
33% (2)
0% (0)
1992
71% (5)
0% (0)
29% (2)
0% (0)
1993
59% (10)
0% (0)
41% (7)
0% (0)
1994
50% (2)
0% (0)
50% (2)
0% (0)
1995
42% (5)
0% (0)
58% (7)
0% (0)
1996
23% (3)
0% (0)
77% (10)
0% (0)
1997
18% (4)
5% (1)
77% (17)
0% (0)
1998
20% (3)
0% (0)
80% (12)
0% (0)
1999
22% (5)
4% (1)
74% (17)
0% (0)
2000
76% (57)
24% (18)
0% (0)
0% (0)
2001
75% (59)
24% (19)
1% (1)
0% (0)
2002
76% (115)
24% (36)
0% (0)
0% (0)
2003
80% (131)
20% (33)
0% (0)
0% (0)
2004
81% (150)
19% (35)
1% (1)
0% (0)
2005
80% (163)
20% (40)
0% (0)
0% (0)
2006
82% (143)
18% (31)
0% (0)
0% (0)
2007
77% (171)
23% (50)
0% (0)
0% (0)
2008
80% (133)
20% (33)
0% (0)
0% (0)
2009
85% (167)
14% (27)
2% (3)
0% (0)
2010
87% (195)
13% (29)
0% (0)
0% (0)
2011
88% (160)
11% (20)
1% (2)
0% (0)
2012
87% (141)
12% (20)
1% (2)
0% (0)
2013
90% (166)
10% (18)
0% (0)
0% (0)
2014
91% (127)
8% (11)
1% (2)
0% (0)
2015
88% (130)
8% (12)
3% (5)
0% (0)
2016
93% (152)
6% (10)
1% (2)
0% (0)
2017
98% (124)
2% (3)
0% (0)
0% (0)
2018
90% (116)
9% (12)
1% (1)
0% (0)
2019
88% (129)
8% (12)
3% (5)
0% (0)
2020
82% (174)
9% (19)
9% (19)
0% (0)
2021
76% (174)
14% (31)
9% (21)
1% (3)
2022
78% (135)
13% (23)
9% (16)
0% (0)
2023
74% (147)
19% (38)
8% (15)
0% (0)
2024
68% (108)
20% (32)
11% (18)
0% (0)
2025
70% (72)
13% (13)
17% (18)
0% (0)
2026
56% (41)
21% (15)
23% (17)
0% (0)
Visualizing deaths by gender
I created a summary data set of remains recoverd for the years 2000 to 2026 (noting that 2026 is incomplete). This summary data set makes it easy to visualize migrant deaths by year and gender.
Rows: 27 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
dbl (4): year, male, female, undetermined
ℹ 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.
ggplot(mdg, aes(x = year)) +geom_line(aes(y = male, color="Male"), size=1) +geom_line(aes(y = female, color="Female"), size=1) +geom_line(aes(y = undetermined, color="Undetermined"), size=1) +scale_color_manual(values =c("coral2", "lightskyblue3", "gray")) +scale_x_continuous(n.breaks =20) +labs(title="Percentage of migrant deaths accounted for by gender, \n2000-July, 2026",y="Percent of total", x="Year",color="") +theme_bw()
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
Below are two barplots. The first shows the total number of remains recovered by gender; the second is a stacked barplot of deaths by year and gender.
ggplot(md, aes(x=gender)) +geom_bar(fill ="lightskyblue4") +labs(title="Migrant remains recovered by year on the Arizona/Mexico border, \n2000-July, 2026", subtitle="Data from the Arizona OpenGIS Project",y="Number recovered", x="Year") +theme_classic()
In the stacked barplot, the total height of the bar corresponds to the total number of remains recovered by year. The color codes represent the number associated with males, females, and those whose gender is undetermined.
stack_gender<-ggplot(md, mapping =aes(x = year)) +geom_bar(aes(fill = gender)) +labs(title="Migrant remains by gender recovered on the Arizona/Mexico border, \n1981-July, 2026", subtitle="Data from the Arizon OpenGIS Project",y="Number recovered", x="Year") +theme_classic() stack_gender <- stack_gender +theme(axis.text.x =element_text(angle =90, vjust =0.5, hjust=1))stack_gender
Age of deceased migrants
The OpenGIS data records the age of the migrant if this determination is possible. To understand the relationship between age and migrant deaths, I’ve created a new variable recording agegroups. These groups are in 5-year increments with the exception of the first group (0-9 years) and the last group (60 and over). We see that about 38% of the migrant deaths have an indeterminant age. We can see that most migrant deaths (about 32% of the total) are in the age range of 20 to 34 years of age. This percentage is based on including all of the individuals whose age cannot be determined. This group constitutes 40% of the data.
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
md$agegroup
n
percent
valid_percent
0-9
7
0%
0%
10-14
22
0%
1%
15-19
255
6%
9%
20-24
498
11%
18%
25-29
490
11%
18%
30-34
470
10%
17%
35-39
391
9%
14%
40-44
281
6%
10%
45-49
168
4%
6%
49-54
85
2%
3%
55-59
40
1%
1%
60 and over
24
1%
1%
NA
1774
39%
-
Below, I tabulate migrant deaths by year and age group. To understand the resulting table, consider the year 2006 and the age group 20-24 years of age. This group accounted for about 13% of that year’s migrant deaths. For most years, those whose age is undetermined (i.e. the “NAs”) accounts for a large share of migrant deaths. Note that the number of “non-determined” seems to be increasing with time.
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
year/agegroup
0-9
10-14
15-19
20-24
25-29
30-34
35-39
40-44
45-49
49-54
55-59
60 and over
NA_
Total
1981
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (1)
100% (1)
1982
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (1)
100% (1)
1985
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (3)
100% (3)
1987
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (1)
100% (1)
1990
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (9)
100% (9)
1991
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (6)
100% (6)
1992
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (7)
100% (7)
1993
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (17)
100% (17)
1994
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (4)
100% (4)
1995
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (12)
100% (12)
1996
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (13)
100% (13)
1997
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (22)
100% (22)
1998
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (15)
100% (15)
1999
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
0% (0)
100% (23)
100% (23)
2000
0% (0)
4% (3)
11% (8)
17% (13)
19% (14)
12% (9)
4% (3)
8% (6)
5% (4)
1% (1)
0% (0)
0% (0)
19% (14)
100% (75)
2001
0% (0)
0% (0)
6% (5)
19% (15)
13% (10)
13% (10)
6% (5)
9% (7)
6% (5)
3% (2)
0% (0)
0% (0)
25% (20)
100% (79)
2002
0% (0)
3% (4)
11% (17)
15% (22)
13% (20)
11% (17)
11% (17)
7% (10)
5% (8)
3% (4)
1% (2)
0% (0)
20% (30)
100% (151)
2003
1% (2)
0% (0)
10% (16)
15% (25)
14% (23)
10% (17)
9% (15)
8% (13)
2% (3)
1% (2)
2% (3)
1% (2)
26% (43)
100% (164)
2004
0% (0)
1% (1)
7% (13)
13% (24)
17% (31)
9% (16)
10% (18)
10% (19)
3% (5)
2% (3)
2% (3)
1% (1)
28% (52)
100% (186)
2005
0% (1)
1% (3)
10% (20)
15% (30)
11% (23)
12% (25)
10% (21)
5% (10)
5% (11)
3% (7)
0% (1)
0% (0)
25% (51)
100% (203)
2006
1% (1)
3% (5)
8% (14)
13% (23)
10% (17)
11% (19)
7% (13)
7% (12)
2% (4)
2% (3)
1% (2)
1% (2)
34% (59)
100% (174)
2007
0% (0)
1% (2)
8% (18)
9% (20)
13% (28)
14% (30)
13% (28)
8% (17)
2% (5)
3% (6)
1% (2)
1% (3)
28% (62)
100% (221)
2008
1% (1)
1% (1)
10% (17)
13% (22)
11% (18)
13% (21)
6% (10)
8% (13)
4% (7)
4% (7)
1% (1)
1% (1)
28% (47)
100% (166)
2009
0% (0)
1% (1)
8% (16)
10% (20)
14% (27)
15% (29)
11% (21)
3% (6)
2% (4)
2% (4)
2% (3)
1% (1)
33% (65)
100% (197)
2010
0% (0)
0% (0)
6% (13)
12% (27)
16% (36)
11% (25)
13% (28)
8% (18)
5% (12)
1% (2)
0% (0)
0% (0)
28% (63)
100% (224)
2011
0% (0)
0% (0)
5% (9)
9% (16)
12% (21)
12% (21)
11% (20)
7% (12)
3% (5)
1% (2)
1% (1)
1% (1)
41% (74)
100% (182)
2012
0% (0)
1% (1)
4% (7)
6% (9)
13% (22)
12% (20)
9% (15)
8% (13)
4% (7)
2% (3)
1% (1)
0% (0)
40% (65)
100% (163)
2013
0% (0)
0% (0)
5% (9)
9% (16)
11% (20)
10% (18)
12% (22)
5% (10)
3% (5)
2% (3)
1% (2)
1% (1)
42% (78)
100% (184)
2014
0% (0)
0% (0)
4% (6)
6% (9)
8% (11)
6% (9)
5% (7)
7% (10)
4% (6)
3% (4)
1% (2)
0% (0)
54% (76)
100% (140)
2015
0% (0)
0% (0)
6% (9)
12% (18)
8% (12)
12% (17)
12% (17)
5% (7)
2% (3)
3% (4)
1% (2)
1% (1)
39% (57)
100% (147)
2016
0% (0)
0% (0)
2% (3)
13% (21)
9% (14)
12% (19)
10% (16)
3% (5)
4% (6)
1% (1)
2% (3)
1% (2)
45% (74)
100% (164)
2017
0% (0)
0% (0)
2% (3)
9% (11)
9% (12)
11% (14)
5% (6)
3% (4)
5% (6)
1% (1)
1% (1)
0% (0)
54% (69)
100% (127)
2018
0% (0)
0% (0)
4% (5)
5% (7)
9% (12)
9% (11)
9% (12)
9% (11)
7% (9)
2% (2)
2% (3)
1% (1)
43% (56)
100% (129)
2019
1% (1)
0% (0)
3% (4)
12% (18)
9% (13)
10% (15)
10% (14)
3% (5)
3% (5)
3% (5)
0% (0)
1% (1)
45% (65)
100% (146)
2020
0% (0)
0% (0)
5% (11)
13% (27)
8% (17)
9% (20)
8% (16)
6% (12)
3% (7)
0% (1)
0% (1)
0% (1)
47% (99)
100% (212)
2021
0% (0)
0% (0)
4% (10)
12% (27)
10% (23)
9% (21)
8% (19)
5% (12)
4% (10)
1% (2)
1% (2)
0% (0)
45% (103)
100% (229)
2022
0% (0)
0% (0)
4% (7)
12% (21)
11% (19)
13% (22)
5% (8)
10% (17)
3% (6)
1% (1)
1% (1)
1% (1)
41% (71)
100% (174)
2023
1% (1)
1% (1)
3% (6)
15% (29)
11% (21)
12% (24)
11% (21)
5% (9)
7% (13)
4% (8)
1% (1)
1% (2)
32% (64)
100% (200)
2024
0% (0)
0% (0)
3% (5)
11% (18)
9% (15)
9% (15)
7% (11)
9% (15)
5% (8)
2% (3)
1% (1)
2% (3)
41% (64)
100% (158)
2025
0% (0)
0% (0)
2% (2)
5% (5)
4% (4)
4% (4)
5% (5)
6% (6)
3% (3)
3% (3)
1% (1)
0% (0)
68% (70)
100% (103)
2026
0% (0)
0% (0)
3% (2)
7% (5)
10% (7)
3% (2)
4% (3)
3% (2)
1% (1)
1% (1)
1% (1)
0% (0)
67% (49)
100% (73)
Total
0% (7)
0% (22)
4% (255)
7% (498)
7% (490)
7% (470)
6% (391)
4% (281)
3% (168)
1% (85)
1% (40)
0% (24)
59% (1,774)
100% (4,505)
Below I produce a barplot of migrant deaths by age group (including those whose age cannot be determined).
ggplot(md, aes(agegroup)) +geom_bar(fill ="lightskyblue4") +labs(title="Migrant remains by age group, 1981-July, 2026", subtitle="Data from the Arizon OpenGIS Project (https://humaneborders.info/app/map.asp)",y="Number recovered", x="Age group") +theme_classic()
Below, I reproduce the age plot but omit the cases where age is indeterminate. This thus gives the number of remains recovered accounted for each group among those whose age is determined.
md_ssage<-md[which(md$agegroup!="NA"),]ggplot(md_ssage, aes(agegroup)) +geom_bar(fill ="lightskyblue4") +labs(title="Migrant remains by age group (excluding NAs), \n2000-July, 2026", subtitle="Data from the Arizon OpenGIS Project (https://humaneborders.info/app/map.asp)",y="Number recovered", x="Age group") +theme_classic() +theme(axis.text.x =element_text(angle =45, vjust =0.5, hjust=.5))
Suppose we are interested in see if there are any trends in average ages of deceased migrants? One way to do this is through a simple regression model. This model will give us an estimate of the average age conditional on year. The key thing to look at is the dots plotted for each year. This dot is our estimate of the average age of death for migrants whose remains are recovered. So consider the year 2003. This dot is right on the line corresponding to 30 years of age. This implies that among those whose remains were recovered in 2003, the average age at death was about 30 years of age. The vertical lines above and below the dot correspond to the error bars around the estimate. In general, the plot reveals that there has been no appreciable change over time in the average age at death. The year 2018 seems to be an outlier, but all other years give estimates in the range of late 20s to early 30s.
regage<-plot_model(agemod, type ="pred", terms =c("YEAR"), ci.lvl = .95, title="Estimated average age of deceased migrants, \n2000-July, 2026", axis.title=c("Year", "Age"), colors=c("skyblue4"), legend.title="Time Period") +geom_line() +theme_bw() regage <- regage +theme(axis.text.x =element_text(angle =90, vjust =0.5, hjust=1))regage
Understanding cause of death
The OpenGIS data records the likely cause of death. Several codes are given for migrant deaths. Two of these codes suggest cause of death is not determined. In one case, it is simply impossible to determine how the migrant dies and in the second case, only skeletal remains are found (making it extraordinarily difficult to discern cause of death). Below, I create a table of the causes of death reported in the OpenGIS data.
tabyl(md$`Cause of Death`)%>%adorn_pct_formatting(rounding ="half up", digits =0) %>% knitr::kable()
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
md$Cause of Death
n
percent
valid_percent
Asphyxia
8
0%
0%
Blunt Force Injury
222
5%
5%
Diabetes
5
0%
0%
Drowning
45
1%
1%
Drug Overdose
7
0%
0%
Exposure
1547
34%
35%
Exsanguination
1
0%
0%
Gunshot Wound
98
2%
2%
Heart Disease
26
1%
1%
Lightning Strike
1
0%
0%
Motor Vehicle Accident
25
1%
1%
Nonviable Fetus
2
0%
0%
Other Disease
24
1%
1%
Other Injury
13
0%
0%
Other Injury / Homicide
26
1%
1%
Other injury
2
0%
0%
Pending
29
1%
1%
Pregnancy Complication
1
0%
0%
Skeletal Remains
1576
35%
36%
Undetermined
720
16%
16%
NA
127
3%
-
Because “exposure,” “skeletal remains,” and “undetermined” are the three dominant codes given, let’s consider how these kinds of cases vary across the years. Observe that the number of “skeletal remains” seem to be increasing with time. I suspect this is due to the number of search and recovery groups heading out into the desert in the past few years. I think we are likely finding the remains of people who may have died long before the calendar year in which their remains were recovered. There is more evidence of this below.
md$cod <-case_when(md$`Cause of Death`=="Exposure"~'Exposure', md$`Cause of Death`=="Skeletal Remains"~'Skeletal remains', md$`Cause of Death`=="Undetermined"~'Undetermined',TRUE~'Other' )my_tabyl<-tabyl(md, year, cod) %>%adorn_percentages(denominator ="row")%>%#adorn_totals(c("row", "col")) %>%adorn_percentages("row") %>%adorn_pct_formatting(rounding ="half up", digits =0) %>%#adorn_ns() %>%#adorn_title("combined") %>% knitr::kable()
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
#clean_df <- as.data.frame(my_tabyl)# 3. Export directly to a clean CSV file#write.csv(clean_df, "cod2000_2026.csv", row.names = FALSE)
I created a summary data set using the information in the table we just considered and plot the number of individuals whose cause-of-death is exposure and all other causes combined (i.e. the category “other”), along with those whose cause-of-death is not determined or is classified as skeletal remains. Two points are very clear. First, relative to all known causes of death, exposure deaths are dominant. Relative to the category “other,” there is a consistent and often large gap. Second, the number of skeletal remains has increased over time. Again, I suspect this is due to greater vigilance in trying to find remains of those who have been lost.
Rows: 27 Columns: 5
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
dbl (5): year20, Exposure, Other, Skeletal remains, Undetermined
ℹ 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.
summary(mdcod)
year20 Exposure Other Skeletal remains
Min. :2000 Min. :0.1000 Min. :0.0200 Min. :0.0200
1st Qu.:2006 1st Qu.:0.2150 1st Qu.:0.0600 1st Qu.:0.1200
Median :2013 Median :0.3300 Median :0.1000 Median :0.4400
Mean :2013 Mean :0.3496 Mean :0.1219 Mean :0.3663
3rd Qu.:2020 3rd Qu.:0.4300 3rd Qu.:0.1600 3rd Qu.:0.5650
Max. :2026 Max. :0.6800 Max. :0.3200 Max. :0.7500
Undetermined
Min. :0.0000
1st Qu.:0.1100
Median :0.1400
Mean :0.1619
3rd Qu.:0.2100
Max. :0.4100
mdcod$sr<-mdcod$`Skeletal remains`*100mdcod$Undetermined<-mdcod$Undetermined*100mdcod$Exposure<-mdcod$Exposure*100mdcod$Other<-mdcod$Other*100ggplot(mdcod, aes(x = year20)) +geom_line(aes(y = Exposure, color="Exposure"), size=.7) +geom_line(aes(y = Other, color="Other"), size=.7) +geom_line(aes(y = sr, color="Skeletal remains"), size=.7) +geom_line(aes(y = Undetermined, color="Undetermined"), size=.7) +scale_color_manual(values =c("coral2", "gray","lightskyblue3", "black")) +scale_x_continuous(n.breaks =20) +labs(title="Percentage of migrant deaths by cause of death, \n2000-July, 2026",y="Percent of total", x="Year",color="") +theme_bw()
Making sense of the PMI
In the OpenGIS data, the Medical Examiner records the likely “post-mortem interval.” This is essentially the time frame in which a migrant likely died. Thus, the code “< 1 day” would imply the migrant died within the past 24 hours, while the code “> 6-8 months” means that the migrant likely died, potentially, much further back in time. Below is a table of the data. One way I like to think of the death data is in terms of what I call “contemporaneous deaths.” By this I mean deaths that likely occurred within a 12-month window. To explain, suppose a migrant remains are found in February? Suppose PCOME assesses the PMI as death occuring less than 6-8 months later. This would seem to imply that the migrant didn’t die in the year his/her death is recorded, but did die within a 12-month window. For those whose PMI is assessed as being greater than 6-8 months, it’s impossible to determine what the time window is in which they died. It could be one year or 15 years.
#Drop years before 2000md <- md %>%filter(year >=2000)tabyl(md$`Post Mortem Interval`) %>%adorn_pct_formatting(rounding ="half up", digits =0) %>% knitr::kable()
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
md$Post Mortem Interval
n
percent
valid_percent
< 1 day
1207
28%
29%
< 1 week
479
11%
12%
< 3 months
359
8%
9%
< 3 weeks
277
6%
7%
< 5 weeks
383
9%
9%
< 6-8 months
398
9%
10%
> 6-8 months
1051
24%
25%
NA
217
5%
-
One thing that may be of interest might be to consider how the PMI codes vary with respect to time. Below I create a table to inspect this.
md$pmi<-md$`Post Mortem Interval`tabyl(md, year, pmi)
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
To simplify our understanding of the data, consider the table that’s created below. Here I compare cases where the PMI is 5 weeks or less to cases that are: 3 months or less; 6-8 months or less; and cases greater than 6-8 months.
md$pmi2 <-case_when(md$`Post Mortem Interval`=="< 1 day"~'A. Within 5 weeks', md$`Post Mortem Interval`=="< 1 week"~'A. Within 5 weeks', md$`Post Mortem Interval`=="< 3 weeks"~'A. Within 5 weeks', md$`Post Mortem Interval`=="< 5 weeks"~'A. Within 5 weeks', md$`Post Mortem Interval`=="< 3 months"~'B. Within 3 months', md$`Post Mortem Interval`=="< 6-8 months"~'C. Within 6-8 months', md$`Post Mortem Interval`=="> 6-8 months"~'D. Greater than 6-8 months',TRUE~'E. Not reported' )
stack_pmi2<-ggplot(md, mapping =aes(x =factor(year))) +geom_bar(aes(fill = pmi2)) +labs(title="Migrant remains by PMI recovered on the Arizona/Mexico border, \n2000-July, 2026", subtitle="Data from the Arizon OpenGIS Project",y="Number recovered", x="Year") +theme_classic() stack_pmi2 <- stack_pmi2 +theme(axis.text.x =element_text(angle =90, vjust =0.5, hjust=1))stack_pmi2
Contemporaneous deaths
#Create variable for contemporaneous deaths (remains recovered less #than 6-8 months)md <- md %>%mutate(contemporaneous =case_when( pmi2 =="A. Within 5 weeks"~1, pmi2 =="B. Within 3 months"~1, pmi2 =="C. Within 6-8 months"~1,TRUE~0# Default catch-all condition ))table(md$contemporaneous)
0 1
1268 3103
md <- md %>%filter(contemporaneous==1)yearplot2<-ggplot(md, aes(factor(year))) +geom_bar(fill ="lightskyblue4") +labs(title="Contemporaneous remains recovered by year along the Arizona/Mexico border, 2000 - July, 2026", subtitle="Data from the Arizon OpenGIS Project (https://humaneborders.info/app/map.asp)",y="Number recovered", x="Year") +theme_classic()yearplot2<- yearplot2 +theme(axis.text.x =element_text(angle =90, vjust =0.5, hjust=1))yearplot2
Below I explore the idea of contemporaneous deaths. I create a variable that records migrant remains recovered within 6 to 8 months (this would include all migrant recoveries on any time-scale up to and within 6-8 months). I take this as a measure of contemporaneous deaths, or alternatively, deaths that likely occurred within a 12-month time window.
To understand the composition of contemporaneous deaths with older remains recovered, I simply take a ratio of contemporaneous deaths with older deaths. To understand the logic, consider the hypothetical example: suppose in year \(x\), there were 75 contemporaneous deaths and 75 older deaths. This would give a ratio of \(\frac{75}{75}=1\), implying that for every 1 recent death recorded, there is 1 older death recorded. Thus, as this ratio exceeds 1, there are more recent deaths recovered than older remains recovered. Below, I plot this ratio by calendar year. For this exercise, I do not include PMI codes listed as undetermined.
The plot shows some interesting characteristics of the death crisis. In the early years, recoveries were overwhelmingly contemporaneous. However, there are several possible reasons for this. First, migrant crossings through Arizona were likely relatively low in the early period implying most deaths would be recent deaths. Second, as the crisis evolves, more and more cross but more and more die without a trace. As time moves forward, and individuals and groups are more vigilant in looking for remains, we begin to find more and more older remains. In my view, the fact that older remains are now being found at a higher rate is alarming for it tells me that the scope and magnitude of the death crisis was far bigger than the reported data in the early years suggested.
Rows: 27 Columns: 10
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
dbl (10): Year, lt1day, lt1week, lt3months, lt3weeks, lt5weeks, lt6_8months,...
ℹ 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.
summary(mdpmi)
Year lt1day lt1week lt3months lt3weeks
Min. :2000 Min. :11.0 Min. : 2.00 Min. : 1.00 Min. : 0.00
1st Qu.:2006 1st Qu.:22.0 1st Qu.:12.00 1st Qu.: 5.50 1st Qu.: 6.00
Median :2013 Median :39.0 Median :17.00 Median :12.00 Median :11.00
Mean :2013 Mean :44.7 Mean :17.74 Mean :13.33 Mean :10.26
3rd Qu.:2020 3rd Qu.:61.0 3rd Qu.:23.00 3rd Qu.:20.50 3rd Qu.:13.50
Max. :2026 Max. :95.0 Max. :32.00 Max. :28.00 Max. :31.00
lt5weeks lt6_8months gt6_8months undetermined
Min. : 2.00 Min. : 1.00 Min. : 3.00 Min. : 0.000
1st Qu.:11.50 1st Qu.: 7.00 1st Qu.:30.00 1st Qu.: 4.000
Median :14.00 Median :15.00 Median :35.00 Median : 7.000
Mean :14.15 Mean :14.74 Mean :39.26 Mean : 7.556
3rd Qu.:18.00 3rd Qu.:22.00 3rd Qu.:48.50 3rd Qu.:10.000
Max. :26.00 Max. :33.00 Max. :97.00 Max. :19.000
Total
Min. : 73.0
1st Qu.:142.0
Median :164.0
Mean :161.7
3rd Qu.:191.5
Max. :225.0
ggplot(mdpmi, aes(x = Year)) +geom_line(aes(y = ratio_inout12)) +scale_x_continuous(n.breaks =20) +labs(title="Ratio of recent remains recovered to older remains recovered, \n2000-July, 2026",subtitle="2026 only includes data up through July 31, 2026",y="Ratio", x="Year") +geom_hline(yintercept =1) +theme_bw()
To get a sense of what recent remains recovered looks like compared to older remains recovered, I plot both time series. There is one crucial remark to make. Earlier in this report, I wrote: “Note that the years 2020 and 2021—years enmeshed in the COVID pandemic—mark two of the three highest recovery years in the times series. I will have more to say on this below.” This statement is a bit misleading (and news reports at this time would have been misleading) and underscores the need to think about the PMI information.
In 2020, while there were indeed many deaths that occurred within in the year (especially June), there were many older remains recovered. Since we cannot unambiguously determine the time frame of death for these older remains, adding them to the contemporaneous deaths may be misleading. Saying this, 2021 and 2023 would rank among the most lethal years in the time-series in terms of contemporaneous deaths.
ggplot(mdpmi, aes(x = Year)) +geom_line(aes(y = in12window, color="Likely within 12 months"), size=.7) +geom_line(aes(y = outside12window, color="Likely outside 12 months"), size=.7) +scale_color_manual(values =c("coral2", "lightskyblue3")) +scale_x_continuous(n.breaks =20) +labs(title="Number of migrant remains recovered for recent vs. older PMI, \n2000-July, 2026",subtitle="2026 only includes data up through July 31, 2026",y="Total", x="Year",color="") +theme_bw()