Text can be beautiful
By: Josh Taylor
Tags: Visualization, Modern Slavery, Machine Learning, Python, NLP

Identifying company commitments with SpaCy

In order to understand what companies are actively doing and committing to doing, we will need to create an intelligent way of identifying such commitments in each Modern Slavery return.

A typical return will include a lot of non-relevant information such as background on the Company and the Modern Slavery Act. Thankfully using the SpaCy NLP library, we can filter these out using its powerful matching features.

The problem with text matching is that it can quickly become burdensome, even when using techniques like regular expression. The issue is all of the different combinations of phrases you need to consider when looking to find even a simple pattern. For example, we are interested in identifying phrases which contain statements like:

"We are committed to..."

However the below phrases would be of interest as well, how can we include these in our analysis without having to write code for every example?

"We promise to"

"We have committed to"

"We will continue to"

"[COMPANY NAME] has committed to"

"[COMPANY NAME] has implemented"

Part of Speech matching

The matching engine in SpaCy allows you to use Part of Speech (POS) tags to match phrases to a specific pattern, for example, rather than searching for specific words, we could filter for a sequence of POS tags:

PRON, VERB, VERB

This matching identifies the following phrase from a snippet from a Modern Slavery return:

Even using a very simple POS filter we can identify phrases which denote commitments made from businesses in their Modern Slavery returns. The match here is highlighted in yellow.

SpaCy even provides an online tool for helping to build and review the results of different rules:

SpaCy’s rule based matcher

It does not take long at all to create a set of rules which produce good results. The below code implements these rules and returns the whole sentence where a result has been identified:

def collect_sents(matcher, doc, i, matches):

match_id, start, end = matches[i]

span = doc[start:end] # Matched span

sent = span.sent # Sentence containing matched span

# Append mock entity for match in displaCy style to matched_sents

# get the match span by ofsetting the start and end of the span with the

# start and end of the sentence in the doc

match_ents = [{

"start": span.start_char - sent.start_char,

"end": span.end_char - sent.start_char,

"label": "MATCH",

}]

matched_sents.append({"text": sent.text, "ents": match_ents})





matcher = Matcher(nlp.vocab)

#this type of pattern matching requires SpaCy >2.1: pattern = [{'POS': {'IN': ['PROPN', 'PRON']}, 'LOWER': {'NOT_IN': ['they','who','you','it','us']} },

{'POS': 'VERB', 'LOWER': {'NOT_IN': ['may','might','could']} },

{'POS': {'IN': ['VERB', 'DET']}, 'LOWER': {'NOT_IN': ['a']}}]

matcher.add("commit", collect_sents, pattern) pattern = [{'POS': {'IN': ['PROPN','PRON']}, 'LOWER': {'NOT_IN': ['they','who','you','it','us']} },

{'POS': 'VERB', 'LOWER': {'NOT_IN': ['may','might','could']}},

{'POS': 'ADJ'},

{'POS': 'ADP'}]

matcher.add("commit", collect_sents, pattern)

How are different industries tackling Modern Slavery?

Now that we have a set statements filtered for commitments and actions from company submissions what can this tell us about how different industries are responding to Modern Slavery?

For this analysis we will use the fantastic ScatterText library developed by Jason Kessler.

This uses a simple, yet powerful approach to find key words and phrases which separate two categories of text. The results can then be output easily into an interactive visualisation.

The below code filters our modern slavery returns to two high-risk industries: Construction and Retail. It then creates a corpus of text for use in the scatter text visualisation:

#select industries to compare:

ind1 = 'Specialty Retail'

ind2 = 'Construction & Engineering' #Filter into a new df with 3 columns one for industry, one for company and the third containing the text

ftr = (df['Industry'] == ind1) | (df['Industry'] == ind2)

df_corp = df.loc[ftr]

df_corp = df_corp[['Industry','Company','clean text']] #Create a scattertext corpus from the df:

corpus = st.CorpusFromPandas( df_corp,

category_col='Industry',

text_col='clean text',

nlp=nlp).build()

Once this is complete, we can run the below to create an interactive scattertext output:

html = st.produce_scattertext_explorer(corpus,

category='Construction & Engineering',

category_name='Construction & Engineering',

not_category_name=ind1,

width_in_pixels=1600)

open("MS-Visualization.html", 'wb').write(html.encode('utf-8'))

HTML(html)

This produces the following output: