10 September 2016

IN THIS POST WE’LL LOOK AT recent job openings and hires data from the Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS).

R code for selected graphs posted below

Job openings and labor turnover

Let’s start by looking at aggregate national trends for total nonfarm sector. The plot below compares hires, job openings and separations (the sum of quits, layoffs and discharges, and other separations) over time. The JOLTS data begins in December 2000.

In the latest release for July 2016, total job openings in the U.S. were 5.9 million, the highest recorded. The graph below compares hires, job openings and separations.

plot of chunk fig-JOLTS-graph1

We also can compare trends by industry.

plot of chunk fig-JOLTS-graph2

We can break these charts into individual panel plots for hires, job openings and total separations.

plot of chunk fig-JOLTS-graph2bplot of chunk fig-JOLTS-graph2bplot of chunk fig-JOLTS-graph2b

Index the data

In order to make comparisons easier, we can index the data so the series can be compared on the same y axis. Due to the spike in hiring due to the 2010 Census, we’ll omit the Federal category in the graphs to come.

plot of chunk fig-JOLTS-graph3plot of chunk fig-JOLTS-graph3plot of chunk fig-JOLTS-graph3

The Beveridge Curve

If we add the unemployment rate from the BLS Current Population Survey (CPS) we can draw a Beveridge Curve.

Beveridge Curve

Animated gifs

We can also make animated gif versions of these graphs

JOLTS gif

JOLTS gif

JOLTS gif

JOLTS gif

JOLTS gif

JOLTS gif

R code for graphics

Below is the R code I used to generate the graphs above.

The data are conveniently availabe in text files we can download from the BLS directly. This file gives an overview of the different files, which are located here.

Data Preparations

## Load libraries:

library(data.table)
library(dplyr)
library(tidyr)
library(viridis)
library(ggplot2)
library(ggthemes)
library(scales)
library(animation)
library(tweenr)


## Get data from BLS.gov

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"                     ))

## Set up data, merge industry names etc.

#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)                  #industry list
  ind.list1<-unique(jolts.ind[display_level==1,]$industry_code) #industry level 1 list
  ind.list2<-unique(jolts.ind[display_level==2,]$industry_code) #industry level 2 list
  ind.list3<-unique(jolts.ind[display_level==3,]$industry_code) #industry level 3 list
  reg.list<-unique(jolts.series$region_code)  #region list
  elem.list<-unique(jolts.element$dataelement_code) #data element list

  
  #create data:
  my.series<-jolts.series[ seasonal=="S"  &
                            ratelevel_code=="L" & region_code=="00", ]
  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))]

  #Create index to December 2000 (first observations)
  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]
  
  #Create index to December 2007
  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]
  
  #order the industries by employment in 2000
  my.out<-my.out[order(date,-value00),]
  #Create factor (for plot facets)
  my.out[,industry_textf:=factor(industry_text,levels=unique(my.out$industry_text))]

Make the plots

# Total nonfarm plot
ggplot(data=my.out[industry_code==00000 & dataelement_code %in% c("HI","JO","TS"),],
         aes(x=date,y=value,color=dataelement_text))+
  facet_wrap(~dataelement_text)+scale_y_continuous(label=comma)+
  scale_color_viridis(name="",discrete=T,end=0.8)+
  theme(strip.text.x = element_text(size = 8))+
  geom_line(size=1.1)+
  theme_minimal()+theme(legend.position="none")+
  theme(plot.caption=element_text(hjust=0,size=7))+
  theme(strip.text.x = element_text(size = 8))+
  labs(x="", y="",
       subtitle="Total Nonfarm, (Ths, Seasonally-Adjusted)",
       title="Hires, Job Openings, and Separations ",
       caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")
 
# Hires, Job Openings and Separations by Inudstry plot
ggplot(data=my.out[industry_code %in% ind.list2 & dataelement_code %in% c("HI","JO","TS"),],
         aes(x=date,y=value,color=dataelement_text))+
  facet_wrap(~industry_textf,scales="free_y")+
  scale_y_continuous(label=comma)+
  scale_color_viridis(name="",discrete=T,end=0.8)+
  theme(strip.text.x = element_text(size = 8))+
  geom_line(size=0.75)+
  theme_minimal()+theme(legend.position="top")+
  theme(plot.caption=element_text(hjust=0,size=7))+
    theme(strip.text.x = element_text(size = 7))+
    theme(axis.text.x = element_text(size=7),
          axis.text.y = element_text(size=7)  ) +
  labs(x="", y="Ths, Seasonally-adjusted",
       subtitle="by industry",
       title="Hires, Job Openings, and Separations ",
       caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")
 

ggplot(data=my.out[ industry_code %in% ind.list2 & dataelement_code=="HI",],
         aes(x=date,y=value,color=industry_text))+
  facet_wrap(~industry_textf,scales="free_y")+
  geom_line(size=0.85)+
  theme_minimal()+theme(legend.position="none")+
  scale_x_date(limits=c(min(d.list2),max(d.list2)))+  
  theme(plot.caption=element_text(hjust=0,size=7))+
  theme(strip.text.x = element_text(size = 7))+
    theme(axis.text.x = element_text(size=7),
          axis.text.y = element_text(size=7)  ) +
  labs(x="", y="Ths, Seasonally-adjusted",
       subtitle="by industry",
       title="Hires (Ths, Seasonally-Adjusted)",
       caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")
 
ggplot(data=my.out[ industry_code %in% ind.list2 & dataelement_code=="JO",],
         aes(x=date,y=value,color=industry_text))+
  facet_wrap(~industry_textf,scales="free_y")+
  theme(strip.text.x = element_text(size = 8))+
  geom_line(size=0.85)+
  theme_minimal()+theme(legend.position="none")+
  #scale_x_date(limits=c(min(d.list2),max(d.list2)))+  
      scale_x_date(limits=c(as.Date("2007-12-01"),max(d.list)))+
  theme(plot.caption=element_text(hjust=0,size=7))+
  theme(strip.text.x = element_text(size = 7))+
    theme(axis.text.x = element_text(size=7),
          axis.text.y = element_text(size=7)  ) +
  labs(x="", y="Ths, Seasonally-adjusted",
       subtitle="by industry",
       title="Job Openings (Ths, Seasonally-Adjusted)",
       caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")

ggplot(data=my.out[ industry_code %in% ind.list2 & dataelement_code=="TS",],
         aes(x=date,y=value,color=industry_text))+
  facet_wrap(~industry_textf,scales="free_y")+
  theme(strip.text.x = element_text(size = 8))+
  geom_line(size=0.85)+
  theme_minimal()+theme(legend.position="none")+
  #scale_x_date(limits=c(min(d.list2),max(d.list2)))+  
      scale_x_date(limits=c(as.Date("2007-12-01"),max(d.list)))+
  theme(plot.caption=element_text(hjust=0,size=7))+
  theme(strip.text.x = element_text(size = 7))+
    theme(axis.text.x = element_text(size=7),
          axis.text.y = element_text(size=7)  ) +
  labs(x="", y="Ths, Seasonally-adjusted",
       subtitle="by industry",
       title="Total Separations (Ths, Seasonally-Adjusted)",
       caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")

Index the data and and plot

ggplot(data=my.out[(industry_code==100000 |(industry_code !=910000 & industry_code %in% ind.list2) )
                       & dataelement_code=="HI",],
         aes(x=date,y=val07,color=industry_text))+
  facet_wrap(~industry_textf)+
  theme(strip.text.x = element_text(size = 8))+
  scale_y_continuous(limits=c(0,250),breaks=seq(0,350,50))+
  geom_ribbon(alpha=0.5,aes(ymin=100,ymax=val07,fill=industry_text),color=NA)+geom_line(size=0.85)+
  theme_minimal()+theme(legend.position="none")+geom_hline(yintercept=100,linetype=2,color="black",size=0.75)+
  scale_x_date(limits=c(min(d.list2),max(d.list2)))+    theme(plot.caption=element_text(hjust=0))+
  theme(strip.text.x = element_text(size = 8))+
  labs(x="", y="Index, Dec 2007=100",
       subtitle="by industry",
       title="Hires (Seasonally-Adjusted Index, Dec 2007=100)",
       caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")
 
ggplot(data=my.out[(industry_code==100000 |(industry_code !=910000 & industry_code %in% ind.list2) )& 
                         dataelement_code=="JO",],
           aes(x=date,y=val07,color=industry_text))+
    facet_wrap(~industry_textf)+
    scale_y_continuous(limits=c(0,250),breaks=seq(0,350,50))+
    geom_ribbon(alpha=0.5,aes(ymin=100,ymax=val07,fill=industry_text),color=NA)+geom_line(size=0.85)+
    theme_minimal()+theme(legend.position="none")+geom_hline(yintercept=100,linetype=2,color="black",size=0.75)+
    scale_x_date(limits=c(min(d.list2),max(d.list2)))+
    theme(plot.caption=element_text(hjust=0,size=7))+
    theme(strip.text.x = element_text(size = 8))+
    labs(x="", y="Index, Dec 2007=100",
         subtitle="by industry",
         title="Job Openings (Seasonally-Adjusted Index, Dec 2007=100)",
         caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")

    
ggplot(data=my.out[(industry_code==100000 |(industry_code !=910000 & industry_code %in% ind.list2) )& 
                        dataelement_code %in% c("HI","JO")],
           aes(x=date,y=val07,color=dataelement_code))+
    facet_wrap(~industry_textf)+
    scale_color_viridis(name="Hires (Hi) and Job Openings (JO)",discrete=T,end=0.75,begin=0.25)+
    scale_linetype_discrete(guide=FALSE)+
    scale_y_continuous(limits=c(0,250),breaks=seq(0,350,50))+
    #geom_ribbon(alpha=0.5,aes(ymin=100,ymax=val07,fill=dataelement_code),color=NA)+
    geom_line(size=0.85)+

    theme_minimal()+theme(legend.position="bottom")+geom_hline(yintercept=100,linetype=2,color="black",size=0.75)+
    scale_x_date(limits=c(as.Date("2007-12-01"),max(d.list)))+
    theme(plot.caption=element_text(hjust=0,size=7))+
    theme(strip.text.x = element_text(size = 8))+
    labs(x="", y="",
         subtitle="Seasonally-Adjusted Index, Dec 2007=100",
         title="Hires and Job Openings by industry",
         caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")

Beveridge Curve code

For the Beveridge Curve I need to get the CPS data. We can read it from a flat file, but because I was just looking for the aggregate series I downloaded from the BLS page and stored it along with the national total nonfarm job openings rate in a simple text file. You can follow the links below to get the data:

# Load data from text file. 
# data can be found here: http://www.bls.gov/data/

bv<-fread("data/bv.txt")
bv$date<-as.Date(bv$date, format="%m/%d/%Y")
bv$id<-factor(bv$id,levels=unique(bv$id))


# Draw Plot
    ggplot(data=bv,aes(y=jo.rate,x=ur.rate,color=rec,group=code))+
    geom_point(aes(alpha=as.numeric(date)/as.numeric(d.list[i])))+
    scale_alpha_continuous(guide=F)+
    theme_minimal()+coord_cartesian(xlim=c(4,10),ylim=c(1.5,4))+
    scale_color_viridis(name="Recession or\nExpansion?",discrete=T,direction=-1,option="D",end=0.75,begin=0.25)+
    geom_path(alpha=0.5)+ 
  theme(plot.caption=element_text(hjust=0,size=7))+
    labs(x="Unemployment Rate",y="Job Openings Rate",
         title="The Beveridge Curve",
         subtitle="job openings rate vs unemployment rate (SA)",
         caption="@lenkiefer Source: U.S. Bureau of Labor Statistics, Current Population Survey and Job Opening and Labor Turnover Survey")

Animation

The animated charts were made using a combination of the animation and tweenr packages for R.

See my earlier post about tweenr for an introduction to tweenr, and more examples here and here.

#gifs are made in the usual fashion, like so:

d.list2<-unique(my.out[date>="2007-12-01",]$date)
N<-length(d.list2)
oopt = ani.options(interval = 0.1)
saveGIF({for (i in 1:N) {
g<-
  ggplot(data=my.out[(industry_code==100000 |(industry_code !=910000 & industry_code %in% ind.list2) )& 
                       date<=d.list2[i] & dataelement_code=="JO",],
         aes(x=date,y=val07,color=industry_text))+
  facet_wrap(~industry_textf)+
  #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=100,ymax=val07,fill=industry_text),color=NA)+geom_line(size=0.85)+
  theme_minimal()+theme(legend.position="none")+geom_hline(yintercept=100,linetype=2,color="black",size=0.75)+
  scale_x_date(limits=c(min(d.list2),max(d.list2)))+    theme(plot.caption=element_text(hjust=0))+
  #scale_y_log10()+
  labs(x="", y="Index, Dec 2007=100",
       subtitle="by industry",
       title="Job Openings (Index: Dec 2007=100, Seasonally-adjusted)",
       caption="@lenkiefer Source: U.S. Bureau of Labor Statistics Job Openings and Labor Turnover Survey (JOLTS)")
  print(g)
  print(i)
  ani.pause()  }
  for (i2 in 1:10) {
    print(g)
    ani.pause()  }
  },movie.name="tween jolts jo industry.gif",ani.width = 800, ani.height = 450)