LET’S TAKE A LOOK AT RECENT LABOR MARKET TRENDS IN THE UNITED STATES. Below we’ll plot labor market trends using the U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS).
Last year we looked at how to get the data and plot it using R. Look to that post for more details, though I’ll include all the code we need below at the bottom.
A trilogy of plots
First, let’s look at some static plots.
Job openings
The first plot shows job openings by industry in the U.S.
We can see that job openings have increased. How about hiring?
Hires
Hiring has increased, but not as much as job openings.
Quits
Quits are a useful measure of confidence in the labor market. If workers feel confident about the labor market they are more likely to quit than if they feel apprehensive about their prospects. Increased quits are a sign of tightening in the labor market. Indeed we see that quits have trended higher.
Animated verisions
R code
The code below creates the plots above.
Get data
First, we need to get the data:
#####################################################################################
## Load libraries ##
#####################################################################################
library(data.table)
library(tidyverse)
#####################################################################################
## Get data from BLS with data.table::fread ##
#####################################################################################
jolts.dt<-fread("http://download.bls.gov/pub/time.series/jt/jt.data.1.AllItems")
jolts.series<-fread("http://download.bls.gov/pub/time.series/jt/jt.series")
jolts.ind<-fread("http://download.bls.gov/pub/time.series/jt/jt.industry",
col.names=c("industry_code","industry_text", "display_level", "selectable","sort_sequence","blank"))
jolts.element<-fread("http://download.bls.gov/pub/time.series/jt/jt.dataelement",
col.names=c("dataelement_code","dataelement_text","display_level","selectable","sort_sequence","blank" ))
#ratelevel: R=rate, L=level
#dataelement_code dataelement_text display_level selectable sort_sequence
#HI Hires 0 T 2
#JO Job openings 0 T 1
#LD Layoffs and discharges 1 T 5
#OS Other separations 1 T 6
#QU Quits 1 T 4
#TS Total separations 0 T 3
#region_code region_text display_level selectable sort_sequence
#00 Total US 0 T 1
#MW Midwest (Only available for Total Nonfarm) 1 T 4
#NE Northeast (Only available for Total Nonfarm) 1 T 2
#SO South (Only available for Total Nonfarm) 1 T 3
#WE West (Only available for Total Nonfarm) 1 T 5
#industries
ind.list<-unique(jolts.series$industry_code)
ind.list1<-unique(jolts.ind[display_level==1,]$industry_code)
ind.list2<-unique(jolts.ind[display_level==2,]$industry_code)
ind.list3<-unique(jolts.ind[display_level==3,]$industry_code)
reg.list<-unique(jolts.series$region_code)
elem.list<-unique(jolts.element$dataelement_code)
jolts.series[seasonal=="S" & dataelement_code=="HI" &
ratelevel_code=="R" & region_code=="00", ]
my.series<-jolts.series[seasonal=="S" &
ratelevel_code=="L" & region_code=="00", ]
#####################################################################################
## data manipulation ##
#####################################################################################
my.out<-jolts.dt[ series_id %in% my.series$series_id,]
my.out<-merge(my.out,jolts.series[,list(series_id,industry_code,dataelement_code)],by="series_id")
my.out<-merge(my.out,jolts.ind[,list(industry_code,industry_text)],by="industry_code")
my.out[,month:=as.numeric(substr(period,2,3))]
my.out[,date:= as.Date(ISOdate(year,month,1))]
bdata<-my.out[year==2000 & month==12,]
bdata<-dplyr::rename(bdata, value00=value)
bdata<-bdata[, c('value00','series_id'), with = FALSE]
my.out<-merge(my.out,bdata,by="series_id")
my.out[,val00:=100*value/value00]
bdata<-my.out[year==2007 & month==12,]
bdata<-dplyr::rename(bdata, value07=value)
bdata<-bdata[, c('value07','series_id'), with = FALSE]
my.out<-merge(my.out,bdata,by="series_id")
my.out[,val07:=100*value/value07]
my.out<-my.out[order(date,-value00),]
my.out[,industry_textf:=factor(industry_text,levels=unique(my.out$industry_text))]
levels(my.out$industry_textf)
d.list<-unique(my.out$date)
d.list2<-unique(my.out[date>="2007-12-01",]$date)
N<-length(d.list2)
Plotting functions
The functions below create our plots
# myplotf() plots job openings
# myplotf2() plots hires
# myplotf3() plots quits
myplotf<-function(i=N){
g<-
ggplot(data=my.out[(industry_code==00000 |(industry_code !=910000 &
industry_code %in% ind.list2) )&
date<=d.list2[i] &
dataelement_code=="JO",],
aes(x=date,y=value,color=industry_text))+
facet_wrap(~industry_textf,scales="free_y")+
geom_line(data=my.out[(industry_code==00000 |(industry_code !=910000 &
industry_code %in% ind.list2) )&
dataelement_code=="JO",],color=NA)+
geom_ribbon(alpha=0.5,aes(ymin=0,ymax=value,fill=industry_text),color=NA)+
geom_line(size=0.5)+
theme_minimal(base_size=8)+theme(legend.position="none")+
geom_hline(data=my.out[(industry_code==00000 |(industry_code !=910000 & industry_code %in% ind.list2) )& date==d.list2[i] & dataelement_code=="JO",],
aes(yintercept=value),linetype=2,color="black",size=0.75)+
scale_x_date(limits=c(min(d.list),max(d.list2)))+
theme(plot.caption=element_text(hjust=0))+
labs(x="", y="Thousands, SA",
subtitle=paste("by industry, dotted line value as of",
as.character(d.list2[i],format="%b-%Y")),
title="Job Openings (Ths, seasonally-adjusted)",
caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")
}
myplotf2<-function(i=N){
g<-
ggplot(data=my.out[(industry_code==00000 |(industry_code !=910000 &
industry_code %in% ind.list2) )&
date<=d.list2[i] & dataelement_code=="HI",],
aes(x=date,y=value,color=industry_text))+
facet_wrap(~industry_textf,scales="free_y")+
geom_line(data=my.out[(industry_code==00000 |(industry_code !=910000 &
industry_code %in% ind.list2) )&
dataelement_code=="HI",],color=NA)+
geom_ribbon(alpha=0.5,aes(ymin=0,ymax=value,fill=industry_text),color=NA)+
geom_line(size=0.85)+
theme_minimal(base_size=8)+theme(legend.position="none")+
geom_hline(data=my.out[(industry_code==00000 |(industry_code !=910000 & industry_code %in% ind.list2) )&
date==d.list2[i] & dataelement_code=="HI",],
aes(yintercept=value),linetype=2,color="black",size=0.75)+
scale_x_date(limits=c(min(d.list),max(d.list2)))+
theme(plot.caption=element_text(hjust=0))+
#scale_y_log10()+
labs(x="", y="Thousands, SA",
subtitle=paste("by industry, dotted line value as of",
as.character(d.list2[i],format="%b-%Y")),
title="Hires (Ths, seasonally-adjusted)",
caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")
}
for (i in seq(1,N,3)) {
file_path = paste0(mydir, "/plot-",5000+i ,".png")
g<-myplotf2(i)
ggsave(file_path, g, width =12, height = 10 , units = "cm",scale=1.5)
print(paste(i,"out of",N))
}
myplotf3<-function(i=N){
ggplot(data=my.out[(industry_code==00000 |(industry_code !=910000 &
industry_code %in% ind.list2) )&
date<=d.list2[i] & dataelement_code=="QU",],
aes(x=date,y=value,color=industry_text))+
facet_wrap(~industry_textf,scales="free_y")+
geom_line(data=my.out[(industry_code==00000 |(industry_code !=910000 & industry_code %in% ind.list2) )&
dataelement_code=="QU",],color=NA)+
#scale_y_continuous(limits=c(25,150),breaks=seq(25,175,25))+
#scale_y_continuous(limits=c(0,250),breaks=seq(0,350,50))+
geom_ribbon(alpha=0.5,aes(ymin=0,ymax=value,fill=industry_text),color=NA)+
geom_line(size=0.85)+
theme_minimal(base_size=8)+theme(legend.position="none")+
geom_hline(data=my.out[(industry_code==00000 |(industry_code !=910000 & industry_code %in% ind.list2) )&
date==d.list2[i] & dataelement_code=="QU",],
aes(yintercept=value),linetype=2,color="black",size=0.75)+
scale_x_date(limits=c(min(d.list),max(d.list2)))+
theme(plot.caption=element_text(hjust=0))+
#scale_y_log10()+
labs(x="", y="Thousands, SA",
subtitle=paste("by industry, dotted line value as of",
as.character(d.list2[i],format="%b-%Y")),
title="Quits (Ths, seasonally-adjusted)",
caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")
}
Call plots
If we just want a static plot, we can just call the functions with \(i=length(dlist)=N\).
If we want an animation, set the variable mydir
to a place where you want to store the image files. Then run:
mydir<- "YOURDIRECTORY" #where you want the images
for (i in seq(1,N,3)) {
file_path = paste0(mydir, "/plot-",5000+i ,".png")
g<-myplotf(i)
ggsave(file_path, g, width =12, height = 10 , units = "cm",scale=1.5)
print(paste(i,"out of",N))
}
g<-myplotf(N)
for (i in (N+1):(N+5)){
file_path = paste0(mydir, "/plot-",5000+i ,".png")
ggsave(file_path, g, width =12, height = 10 , units = "cm",scale=1.5)
print(paste(i,"out of",N))
}
Simply substitute myplotf2()
or myplotf3()
for myplotf()
if you want hires (quits) instead of job openings.
Then navigate to mydir
and call magick convert -delay 10 loop -0 *.png awesomegif.gif
from the command line to create the gif. Alternatively, you could try the magick package.