Anders Swanson Anders Swanson - 1 year ago 66
Python Question

Understanding another's text-mining function that removes similar strings

I’m trying to replicate the methodology from this article, 538 Post about Most Repetitive Phrases, in which the author mined US presidential debate transcripts to determine the most repetitive phrases for each candidate.

I'm trying to implement this methodology with another dataset in R with the


Most of the code (GitHub repository) concerns mining the transcripts and assembling counts of each ngram, but I get lost at the
function code below:

def prune_substrings(tfidf_dicts, prune_thru=1000):

pruned = tfidf_dicts

for candidate in range(len(candidates)):
# growing list of n-grams in list form
so_far = []

ngrams_sorted = sorted(tfidf_dicts[candidate].items(), key=operator.itemgetter(1), reverse=True)[:prune_thru]
for ngram in ngrams_sorted:
# contained in a previous aka 'better' phrase
for better_ngram in so_far:
if overlap(list(better_ngram), list(ngram[0])):
#print "PRUNING!! "
#print list(better_ngram)
#print list(ngram[0])

pruned[candidate][ngram[0]] = 0
# not contained, so add to so_far to prevent future subphrases
so_far += [list(ngram[0])]

return pruned

The input of the function,
, is an array of dictionaries (one for each candidate) with ngrams as keys and tf-idf scores as values. For example, Trump's tf-idf dict begins like this:

trump.tfidf.dict = {'we don't win': 83.2, 'you have to': 72.8, ... }

so the structure of the input is like this:

tfidf_dicts = {trump.tfidf.dict, rubio.tfidf.dict, etc }

MY understanding is that
does the following things, but I'm stuck on the
else if
clause, which is a pythonic thing I don't understand yet.

A. create list : pruned as tfidf_dicts; a list of tfidf dicts for each candidate

B loop through each candidate:

  1. so_far = start an empty list of ngrams gone through so so_far

  2. ngrams_sorted = sorted member's tf-idf dict from smallest to biggest

  3. loop through each ngram in sorted

    • loop through each better_ngram in so_far

      1. IF overlap b/w (below) == TRUE:

        • better_ngram (from so_far) and

        • ngram (from ngrams_sorted)

        • THEN zero out tf-idf for ngram

      2. ELSE if (WHAT?!?)

        • add ngram to list, so_far

C. return pruned, i.e. list of unique ngrams sorted in order

Any help at all is much appreciated!

Answer Source

4 months later but here's my solution. I'm sure there is a more efficient solution, but for my purposes, it worked. The pythonic for-else doesn't translate to R. So the steps are different.

  1. Take top n ngrams.
  2. Create a list, t, where each element of the list is a logical vector of length n that says whether ngram in question overlaps all other ngrams (but fix 1:x to be false automatically)
  3. Cbind together every element of t into a table, t2
  4. Return only elements of t2 row sum is zero set elements 1:n to FALSE (i.e. no overlap)


PrunedList Function

#' GetPrunedList
#' takes a word freq df with columns Words and LenNorm, returns df of nonoverlapping strings
GetPrunedList <- function(wordfreqdf, prune_thru = 100) {
        #take only first n items in list
        tmp <- head(wordfreqdf, n = prune_thru) %>%
                select(ngrams = Words, tfidfXlength = LenNorm)
        #for each ngram in list:
        t <- (lapply(1:nrow(tmp), function(x) {
                #find overlap between ngram and all items in list (overlap = TRUE)
                idx <- overlap(tmp[x, "ngrams"], tmp$ngrams)
                #set overlap as false for itself and higher-scoring ngrams
                idx[1:x] <- FALSE

        #bind each ngram's overlap vector together to make a matrix
        t2 <-, t)   

        #find rows(i.e. ngrams) that do not overlap with those below
        idx <- rowSums(t2) == 0
        pruned <- tmp[idx,]
        rownames(pruned) <- NULL

Overlap function

#' overlap
#' OBJ: takes two ngrams (as strings) and to see if they overlap
#' INPUT: a,b ngrams as strings
#' OUTPUT: TRUE if overlap
overlap <- function(a, b) {
        max_overlap <- min(3, CountWords(a), CountWords(b))

        a.beg <- word(a, start = 1L, end = max_overlap)
        a.end <- word(a, start = -max_overlap, end = -1L)
        b.beg <- word(b, start = 1L, end = max_overlap)
        b.end <- word(b, start = -max_overlap, end = -1L)

        # b contains a's beginning
        w <- str_detect(b, coll(a.beg, TRUE))
        # b contains a's end
        x <- str_detect(b, coll(a.end, TRUE))
        # a contains b's beginning
        y <- str_detect(a, coll(b.beg, TRUE))
        # a contains b's end
        z <- str_detect(a, coll(b.end, TRUE))

        #return TRUE if any of above are true
        (w | x | y | z)
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download