Count words in a list of titles, with some cleanup The 2019 Stack Overflow Developer Survey Results Are In Announcing the arrival of Valued Associate #679: Cesar Manara Planned maintenance scheduled April 17/18, 2019 at 00:00UTC (8:00pm US/Eastern)Program to count the number of lines of codeCount the frequency of words in a text fileGet count of digits of a number that divide the numberWord count and most frequent words from input text, excluding stop wordsRemoving duplicate letters from list of words with Python and List ComprehensionsCalculate probability of word occurenceInsert a number in a sorted list and return the list with the number at the correct indexPython: count number of pairs in array(list)Web scraping the titles and descriptions of trending YouTube videosfind substring with count and return even frequency substring list

"... to apply for a visa" or "... and applied for a visa"?

What other Star Trek series did the main TNG cast show up in?

What was the last x86 CPU that did not have the x87 floating-point unit built in?

What happens to a Warlock's expended Spell Slots when they gain a Level?

For what reasons would an animal species NOT cross a *horizontal* land bridge?

Could an empire control the whole planet with today's comunication methods?

Accepted by European university, rejected by all American ones I applied to? Possible reasons?

How to determine omitted units in a publication

Do warforged have souls?

How to substitute curly brackets with round brackets in a grid of list

Free operad over a monoid object

What information about me do stores get via my credit card?

How did passengers keep warm on sail ships?

Is every episode of "Where are my Pants?" identical?

Why not take a picture of a closer black hole?

How do spell lists change if the party levels up without taking a long rest?

What to do when moving next to a bird sanctuary with a loosely-domesticated cat?

Did the UK government pay "millions and millions of dollars" to try to snag Julian Assange?

Why doesn't a hydraulic lever violate conservation of energy?

Solving overdetermined system by QR decomposition

Word for: a synonym with a positive connotation?

How to support a colleague who finds meetings extremely tiring?

Homework question about an engine pulling a train

Working through Single Responsibility Principle in Python when Calls are Expensive



Count words in a list of titles, with some cleanup



The 2019 Stack Overflow Developer Survey Results Are In
Announcing the arrival of Valued Associate #679: Cesar Manara
Planned maintenance scheduled April 17/18, 2019 at 00:00UTC (8:00pm US/Eastern)Program to count the number of lines of codeCount the frequency of words in a text fileGet count of digits of a number that divide the numberWord count and most frequent words from input text, excluding stop wordsRemoving duplicate letters from list of words with Python and List ComprehensionsCalculate probability of word occurenceInsert a number in a sorted list and return the list with the number at the correct indexPython: count number of pairs in array(list)Web scraping the titles and descriptions of trending YouTube videosfind substring with count and return even frequency substring list



.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








4












$begingroup$


I have a list of article titles, where I wish to count the number of occurrences for each word (and remove some words and characters). The input is in a .csv file where the titles are in column 'Titles'.



Maybe someone could help me do it more elegantly.



import numpy as np
import pandas as pd
#imports Counter, as we will need it later:
from collections import Counter

df = pd.read_csv("Article_titles.csv")
print (df.head(10))

#Selecting the titles into variable
titles = []
titles = df.Title

remove_words_list = ["at","of","a","and","in","for","the","to","with","on","using","an","after","from","by","use","review","upper","new","system"]
remove_characters_list = ".:,-%()[]?'"

huge_title_list = []
#create a list of all article titles:
for i in range(len(titles)):
clean_title = titles[i].lower().translate(ord(i): None for i in remove_characters_list)
huge_title_list.append(clean_title)

total_words_string = " ".join(huge_title_list)
#join all article titles into one huge string

querywords = total_words_string.split()
#split the string into a series of words

resultwords = [word for word in querywords if word not in remove_words_list]
#From stackoverflow

resultwords_as_list = list( Counter(resultwords).items())

#Convert resultwords_list to dataframe, then convert count to numbers and finally sorting.
resultframe = pd.DataFrame(np.array(resultwords_as_list).reshape(-1,2), columns = ("Keyword","Count"))
resultframe.Count = pd.to_numeric(resultframe.Count)
sortedframe = resultframe.sort_values(by='Count',ascending=False).reset_index(drop=True)
print(sortedframe[0:50])


example of Input:



Titles | other_field | other_field2

"Current status of prognostic factors in patients with metastatic renal cell carcinoma." |"asdf"|12
"Sentinel lymph node biopsy in clinically node-negative Merkel cell carcinoma: the Westmead Hospital experience." |"asdf"|15


desired output:



Word | Count

carcinoma | 2

cell | 2

biopsy | 1

clinically | 1

....

...









share|improve this question









New contributor




Jesper Mølgaard is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.







$endgroup$











  • $begingroup$
    Welcome to Code Review! Good job on your first question. I hope you get great answers.
    $endgroup$
    – Alex
    2 hours ago

















4












$begingroup$


I have a list of article titles, where I wish to count the number of occurrences for each word (and remove some words and characters). The input is in a .csv file where the titles are in column 'Titles'.



Maybe someone could help me do it more elegantly.



import numpy as np
import pandas as pd
#imports Counter, as we will need it later:
from collections import Counter

df = pd.read_csv("Article_titles.csv")
print (df.head(10))

#Selecting the titles into variable
titles = []
titles = df.Title

remove_words_list = ["at","of","a","and","in","for","the","to","with","on","using","an","after","from","by","use","review","upper","new","system"]
remove_characters_list = ".:,-%()[]?'"

huge_title_list = []
#create a list of all article titles:
for i in range(len(titles)):
clean_title = titles[i].lower().translate(ord(i): None for i in remove_characters_list)
huge_title_list.append(clean_title)

total_words_string = " ".join(huge_title_list)
#join all article titles into one huge string

querywords = total_words_string.split()
#split the string into a series of words

resultwords = [word for word in querywords if word not in remove_words_list]
#From stackoverflow

resultwords_as_list = list( Counter(resultwords).items())

#Convert resultwords_list to dataframe, then convert count to numbers and finally sorting.
resultframe = pd.DataFrame(np.array(resultwords_as_list).reshape(-1,2), columns = ("Keyword","Count"))
resultframe.Count = pd.to_numeric(resultframe.Count)
sortedframe = resultframe.sort_values(by='Count',ascending=False).reset_index(drop=True)
print(sortedframe[0:50])


example of Input:



Titles | other_field | other_field2

"Current status of prognostic factors in patients with metastatic renal cell carcinoma." |"asdf"|12
"Sentinel lymph node biopsy in clinically node-negative Merkel cell carcinoma: the Westmead Hospital experience." |"asdf"|15


desired output:



Word | Count

carcinoma | 2

cell | 2

biopsy | 1

clinically | 1

....

...









share|improve this question









New contributor




Jesper Mølgaard is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.







$endgroup$











  • $begingroup$
    Welcome to Code Review! Good job on your first question. I hope you get great answers.
    $endgroup$
    – Alex
    2 hours ago













4












4








4





$begingroup$


I have a list of article titles, where I wish to count the number of occurrences for each word (and remove some words and characters). The input is in a .csv file where the titles are in column 'Titles'.



Maybe someone could help me do it more elegantly.



import numpy as np
import pandas as pd
#imports Counter, as we will need it later:
from collections import Counter

df = pd.read_csv("Article_titles.csv")
print (df.head(10))

#Selecting the titles into variable
titles = []
titles = df.Title

remove_words_list = ["at","of","a","and","in","for","the","to","with","on","using","an","after","from","by","use","review","upper","new","system"]
remove_characters_list = ".:,-%()[]?'"

huge_title_list = []
#create a list of all article titles:
for i in range(len(titles)):
clean_title = titles[i].lower().translate(ord(i): None for i in remove_characters_list)
huge_title_list.append(clean_title)

total_words_string = " ".join(huge_title_list)
#join all article titles into one huge string

querywords = total_words_string.split()
#split the string into a series of words

resultwords = [word for word in querywords if word not in remove_words_list]
#From stackoverflow

resultwords_as_list = list( Counter(resultwords).items())

#Convert resultwords_list to dataframe, then convert count to numbers and finally sorting.
resultframe = pd.DataFrame(np.array(resultwords_as_list).reshape(-1,2), columns = ("Keyword","Count"))
resultframe.Count = pd.to_numeric(resultframe.Count)
sortedframe = resultframe.sort_values(by='Count',ascending=False).reset_index(drop=True)
print(sortedframe[0:50])


example of Input:



Titles | other_field | other_field2

"Current status of prognostic factors in patients with metastatic renal cell carcinoma." |"asdf"|12
"Sentinel lymph node biopsy in clinically node-negative Merkel cell carcinoma: the Westmead Hospital experience." |"asdf"|15


desired output:



Word | Count

carcinoma | 2

cell | 2

biopsy | 1

clinically | 1

....

...









share|improve this question









New contributor




Jesper Mølgaard is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.







$endgroup$




I have a list of article titles, where I wish to count the number of occurrences for each word (and remove some words and characters). The input is in a .csv file where the titles are in column 'Titles'.



Maybe someone could help me do it more elegantly.



import numpy as np
import pandas as pd
#imports Counter, as we will need it later:
from collections import Counter

df = pd.read_csv("Article_titles.csv")
print (df.head(10))

#Selecting the titles into variable
titles = []
titles = df.Title

remove_words_list = ["at","of","a","and","in","for","the","to","with","on","using","an","after","from","by","use","review","upper","new","system"]
remove_characters_list = ".:,-%()[]?'"

huge_title_list = []
#create a list of all article titles:
for i in range(len(titles)):
clean_title = titles[i].lower().translate(ord(i): None for i in remove_characters_list)
huge_title_list.append(clean_title)

total_words_string = " ".join(huge_title_list)
#join all article titles into one huge string

querywords = total_words_string.split()
#split the string into a series of words

resultwords = [word for word in querywords if word not in remove_words_list]
#From stackoverflow

resultwords_as_list = list( Counter(resultwords).items())

#Convert resultwords_list to dataframe, then convert count to numbers and finally sorting.
resultframe = pd.DataFrame(np.array(resultwords_as_list).reshape(-1,2), columns = ("Keyword","Count"))
resultframe.Count = pd.to_numeric(resultframe.Count)
sortedframe = resultframe.sort_values(by='Count',ascending=False).reset_index(drop=True)
print(sortedframe[0:50])


example of Input:



Titles | other_field | other_field2

"Current status of prognostic factors in patients with metastatic renal cell carcinoma." |"asdf"|12
"Sentinel lymph node biopsy in clinically node-negative Merkel cell carcinoma: the Westmead Hospital experience." |"asdf"|15


desired output:



Word | Count

carcinoma | 2

cell | 2

biopsy | 1

clinically | 1

....

...






python strings csv pandas






share|improve this question









New contributor




Jesper Mølgaard is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











share|improve this question









New contributor




Jesper Mølgaard is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









share|improve this question




share|improve this question








edited 8 mins ago









200_success

131k17157422




131k17157422






New contributor




Jesper Mølgaard is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









asked 3 hours ago









Jesper MølgaardJesper Mølgaard

211




211




New contributor




Jesper Mølgaard is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





New contributor





Jesper Mølgaard is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






Jesper Mølgaard is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











  • $begingroup$
    Welcome to Code Review! Good job on your first question. I hope you get great answers.
    $endgroup$
    – Alex
    2 hours ago
















  • $begingroup$
    Welcome to Code Review! Good job on your first question. I hope you get great answers.
    $endgroup$
    – Alex
    2 hours ago















$begingroup$
Welcome to Code Review! Good job on your first question. I hope you get great answers.
$endgroup$
– Alex
2 hours ago




$begingroup$
Welcome to Code Review! Good job on your first question. I hope you get great answers.
$endgroup$
– Alex
2 hours ago










1 Answer
1






active

oldest

votes


















1












$begingroup$

Very good first post! There are a few small things to call out. First, refrain from using pandas and numpy unless it's absolutely necessary. Both are fantastic tools which excel in certain areas, but generally speaking many stock Python scripts can be just as performant, much more portable, and a lot more clear on their own. For example, stock Python is fantastically suited for counting the frequency of hashable items (in fact, you used it with Counter). But if I wanted to do QR decomposition, I would absolutely reach for numpy as it already has an implementation of this and that implementation will likely be much faster than anything I can write. Always be sure to use the right tool for the job. Generally speaking, the more dependencies a piece of code has, the harder it is to compose with other things (harder could be quantifying programmer effort here--as in, the effort to install dependencies/debug version issues/etc.). So for this reason, I'm going to remove numpy and pandas from my answer.




#imports Counter, as we will need it later:
from collections import Counter


This is a useless comment. Of course that's what the following line does. And of course you're importing something because you need it. Don't just comment for the sake of commenting. Comment to explain the "why" not the "what."




You can do your csv reading with stock python. You should be using with contexts here to handle properly closing files (especially if exceptions are raised). csv.DictReader will be useful here as it is an iterator that yields dicts for each row. This allows us to use a generator comprehension to build the list of titles. If you are unfamiliar with generators, you should [read up on them}(https://stackoverflow.com/q/1756096/568785). The benefit they give you is that they don't build up a big list in memory. They only produce values upon request. In your code you have a lot of list and string manipulation (see the list comprehension of resultwords, the join/split dance of total_words_string and querywords--which is unnecessary--,and huge_title_list). All of these points can be bottlenecks, because they require building up in memory the entire state to that point. Instead, using a generator allows you to lazily defer the work until you need it (in this case, when you use Counter).



A good analogy for this is an assembly line. Imagine you had a factory building computers with 3 stops on the assembly line (we'll call them A, B, and C). At each stop, a worker adds one component to the computer (let's say motherboard, CPU, RAM). If we were to write this out using lists it would look like this:



computers_after_A = [add_motherboard(computer) for computer in bare_computers]
computers_after_B = [add_CPU(computer) for computer in computer_after_A]
computers_after_C = [add_RAM(computer) for computer in computer_after_B]


This looks innocent enough, but if you were to run your factory like this you'd have some real problems. Let's say the factory processes 1000 computers per day. The above code would be equivalent to the following:



  1. The worker at A adds motherboards to each incoming computer and then adds the finished computers to a big stack.

  2. Once worker A is done with all 1000 computers, the stack is pushed to worker B who takes one from each stack, adds the CPU and then makes a new stack of finished computers.

  3. Once worker B is done with all 1000 computers, the stack of 1000 computers is pushed to worker C, who adds the RAM and then adds the computers to the finally done stack.

The problem with the above is that it is inefficient (worker B has no work until worker A completes all 1000 computers and worker C has no work until both workers A and B finish all 1000 computers) and requires you to have the room to store all 1000 computers at each location A, B, and C.



A much better approach is how factories really work. A constantly moving conveyor belt which moves the computers through one by one. At each location, the worker adds their respective component and then send each computer along individually to the next station. This would be equivalent to the following:



computers_after_A = (add_motherboard(computer) for computer in bare_computers)
computers_after_B = (add_CPU(computer) for computer in computer_after_A)
computers_after_C = (add_RAM(computer) for computer in computer_after_B)


With this approach we've replaced lists with generators. Now, computers are immediately available (instead of only available after everything finishes) and we don't need the space to build up 3 piles of 1000 computers.



Hopefully, this motivates why we should use generators. I'll be using them below.



There's no reason to use pandas to sort the counts from Counter. Counter has a most_common() method that will do this for you.



You had the right idea with using translate to replace unwanted characters. But you don't need to build the dictionary every time. Instead using string.maketrans and saving this "translation dictionary" will avoid doing lots of extra work.



remove_words_list should probably be called stop_list as this is the common parlance for words you want to exclude. Also, you should make this a frozenset so that word in stop_list is O(1) instead of an O(n) scan (what you currently do, which is really inefficient).



Typically, we include code that we want to run in a main() function and run it only if __name__ == '__main__'. This allows other code to include this file (and potentially use an utility functions it defines) without having the main() run.



With all that in mind, I'd refactor your code to something like this:



import csv
import string

STOP_WORDS = frozenset(('at', 'of', 'a', 'and', 'in')) # ...
EXCLUDE_CHARS = string.maketrans('', '', '.:,-%()[]?'')

def main():
with open('Article_titles.csv') as f: # consider accepting the filename as an argument (sys.argv)
titles = (row['title'] for row in csv.DictReader(f, delimter='|'))
words = (word.lower().translate(EXCLDUE_CHARS)
for title in titles for word in title.split())
interesting_words = (word for word in words if word not in STOP_WORDS)
frequencies = Counter(interesting_words)

print(frequencies.most_common())

if __name__ == '__main__':
main()


Now remember how I mentioned above choosing the right tool for the job? It turns out that what you're trying to do (word extraction) is pretty tricky. Natural language has all sorts of irregularities, odd formatting, and inconsistencies. Luckily, there's a fantastic Python library which has a fairly good (complicated) implementation for handling all this for you: nltk. This is a very appropriate use of a library, because this task is hard and nltk has put lots of work into building a robust implementation. The home page has an example of what you need:



>>> from nltk import word_tokenize
>>> word_tokenize('When this thing hits 88 miles per hour')
['When', 'this', 'thing', 'hits', '88', 'miles', 'per', 'hour']


I'll leave integrating nltk as an exercise for you, but it should only involve changing one line in my above code.






share|improve this answer









$endgroup$













    Your Answer






    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "196"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader:
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    ,
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );






    Jesper Mølgaard is a new contributor. Be nice, and check out our Code of Conduct.









    draft saved

    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f217334%2fcount-words-in-a-list-of-titles-with-some-cleanup%23new-answer', 'question_page');

    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    1












    $begingroup$

    Very good first post! There are a few small things to call out. First, refrain from using pandas and numpy unless it's absolutely necessary. Both are fantastic tools which excel in certain areas, but generally speaking many stock Python scripts can be just as performant, much more portable, and a lot more clear on their own. For example, stock Python is fantastically suited for counting the frequency of hashable items (in fact, you used it with Counter). But if I wanted to do QR decomposition, I would absolutely reach for numpy as it already has an implementation of this and that implementation will likely be much faster than anything I can write. Always be sure to use the right tool for the job. Generally speaking, the more dependencies a piece of code has, the harder it is to compose with other things (harder could be quantifying programmer effort here--as in, the effort to install dependencies/debug version issues/etc.). So for this reason, I'm going to remove numpy and pandas from my answer.




    #imports Counter, as we will need it later:
    from collections import Counter


    This is a useless comment. Of course that's what the following line does. And of course you're importing something because you need it. Don't just comment for the sake of commenting. Comment to explain the "why" not the "what."




    You can do your csv reading with stock python. You should be using with contexts here to handle properly closing files (especially if exceptions are raised). csv.DictReader will be useful here as it is an iterator that yields dicts for each row. This allows us to use a generator comprehension to build the list of titles. If you are unfamiliar with generators, you should [read up on them}(https://stackoverflow.com/q/1756096/568785). The benefit they give you is that they don't build up a big list in memory. They only produce values upon request. In your code you have a lot of list and string manipulation (see the list comprehension of resultwords, the join/split dance of total_words_string and querywords--which is unnecessary--,and huge_title_list). All of these points can be bottlenecks, because they require building up in memory the entire state to that point. Instead, using a generator allows you to lazily defer the work until you need it (in this case, when you use Counter).



    A good analogy for this is an assembly line. Imagine you had a factory building computers with 3 stops on the assembly line (we'll call them A, B, and C). At each stop, a worker adds one component to the computer (let's say motherboard, CPU, RAM). If we were to write this out using lists it would look like this:



    computers_after_A = [add_motherboard(computer) for computer in bare_computers]
    computers_after_B = [add_CPU(computer) for computer in computer_after_A]
    computers_after_C = [add_RAM(computer) for computer in computer_after_B]


    This looks innocent enough, but if you were to run your factory like this you'd have some real problems. Let's say the factory processes 1000 computers per day. The above code would be equivalent to the following:



    1. The worker at A adds motherboards to each incoming computer and then adds the finished computers to a big stack.

    2. Once worker A is done with all 1000 computers, the stack is pushed to worker B who takes one from each stack, adds the CPU and then makes a new stack of finished computers.

    3. Once worker B is done with all 1000 computers, the stack of 1000 computers is pushed to worker C, who adds the RAM and then adds the computers to the finally done stack.

    The problem with the above is that it is inefficient (worker B has no work until worker A completes all 1000 computers and worker C has no work until both workers A and B finish all 1000 computers) and requires you to have the room to store all 1000 computers at each location A, B, and C.



    A much better approach is how factories really work. A constantly moving conveyor belt which moves the computers through one by one. At each location, the worker adds their respective component and then send each computer along individually to the next station. This would be equivalent to the following:



    computers_after_A = (add_motherboard(computer) for computer in bare_computers)
    computers_after_B = (add_CPU(computer) for computer in computer_after_A)
    computers_after_C = (add_RAM(computer) for computer in computer_after_B)


    With this approach we've replaced lists with generators. Now, computers are immediately available (instead of only available after everything finishes) and we don't need the space to build up 3 piles of 1000 computers.



    Hopefully, this motivates why we should use generators. I'll be using them below.



    There's no reason to use pandas to sort the counts from Counter. Counter has a most_common() method that will do this for you.



    You had the right idea with using translate to replace unwanted characters. But you don't need to build the dictionary every time. Instead using string.maketrans and saving this "translation dictionary" will avoid doing lots of extra work.



    remove_words_list should probably be called stop_list as this is the common parlance for words you want to exclude. Also, you should make this a frozenset so that word in stop_list is O(1) instead of an O(n) scan (what you currently do, which is really inefficient).



    Typically, we include code that we want to run in a main() function and run it only if __name__ == '__main__'. This allows other code to include this file (and potentially use an utility functions it defines) without having the main() run.



    With all that in mind, I'd refactor your code to something like this:



    import csv
    import string

    STOP_WORDS = frozenset(('at', 'of', 'a', 'and', 'in')) # ...
    EXCLUDE_CHARS = string.maketrans('', '', '.:,-%()[]?'')

    def main():
    with open('Article_titles.csv') as f: # consider accepting the filename as an argument (sys.argv)
    titles = (row['title'] for row in csv.DictReader(f, delimter='|'))
    words = (word.lower().translate(EXCLDUE_CHARS)
    for title in titles for word in title.split())
    interesting_words = (word for word in words if word not in STOP_WORDS)
    frequencies = Counter(interesting_words)

    print(frequencies.most_common())

    if __name__ == '__main__':
    main()


    Now remember how I mentioned above choosing the right tool for the job? It turns out that what you're trying to do (word extraction) is pretty tricky. Natural language has all sorts of irregularities, odd formatting, and inconsistencies. Luckily, there's a fantastic Python library which has a fairly good (complicated) implementation for handling all this for you: nltk. This is a very appropriate use of a library, because this task is hard and nltk has put lots of work into building a robust implementation. The home page has an example of what you need:



    >>> from nltk import word_tokenize
    >>> word_tokenize('When this thing hits 88 miles per hour')
    ['When', 'this', 'thing', 'hits', '88', 'miles', 'per', 'hour']


    I'll leave integrating nltk as an exercise for you, but it should only involve changing one line in my above code.






    share|improve this answer









    $endgroup$

















      1












      $begingroup$

      Very good first post! There are a few small things to call out. First, refrain from using pandas and numpy unless it's absolutely necessary. Both are fantastic tools which excel in certain areas, but generally speaking many stock Python scripts can be just as performant, much more portable, and a lot more clear on their own. For example, stock Python is fantastically suited for counting the frequency of hashable items (in fact, you used it with Counter). But if I wanted to do QR decomposition, I would absolutely reach for numpy as it already has an implementation of this and that implementation will likely be much faster than anything I can write. Always be sure to use the right tool for the job. Generally speaking, the more dependencies a piece of code has, the harder it is to compose with other things (harder could be quantifying programmer effort here--as in, the effort to install dependencies/debug version issues/etc.). So for this reason, I'm going to remove numpy and pandas from my answer.




      #imports Counter, as we will need it later:
      from collections import Counter


      This is a useless comment. Of course that's what the following line does. And of course you're importing something because you need it. Don't just comment for the sake of commenting. Comment to explain the "why" not the "what."




      You can do your csv reading with stock python. You should be using with contexts here to handle properly closing files (especially if exceptions are raised). csv.DictReader will be useful here as it is an iterator that yields dicts for each row. This allows us to use a generator comprehension to build the list of titles. If you are unfamiliar with generators, you should [read up on them}(https://stackoverflow.com/q/1756096/568785). The benefit they give you is that they don't build up a big list in memory. They only produce values upon request. In your code you have a lot of list and string manipulation (see the list comprehension of resultwords, the join/split dance of total_words_string and querywords--which is unnecessary--,and huge_title_list). All of these points can be bottlenecks, because they require building up in memory the entire state to that point. Instead, using a generator allows you to lazily defer the work until you need it (in this case, when you use Counter).



      A good analogy for this is an assembly line. Imagine you had a factory building computers with 3 stops on the assembly line (we'll call them A, B, and C). At each stop, a worker adds one component to the computer (let's say motherboard, CPU, RAM). If we were to write this out using lists it would look like this:



      computers_after_A = [add_motherboard(computer) for computer in bare_computers]
      computers_after_B = [add_CPU(computer) for computer in computer_after_A]
      computers_after_C = [add_RAM(computer) for computer in computer_after_B]


      This looks innocent enough, but if you were to run your factory like this you'd have some real problems. Let's say the factory processes 1000 computers per day. The above code would be equivalent to the following:



      1. The worker at A adds motherboards to each incoming computer and then adds the finished computers to a big stack.

      2. Once worker A is done with all 1000 computers, the stack is pushed to worker B who takes one from each stack, adds the CPU and then makes a new stack of finished computers.

      3. Once worker B is done with all 1000 computers, the stack of 1000 computers is pushed to worker C, who adds the RAM and then adds the computers to the finally done stack.

      The problem with the above is that it is inefficient (worker B has no work until worker A completes all 1000 computers and worker C has no work until both workers A and B finish all 1000 computers) and requires you to have the room to store all 1000 computers at each location A, B, and C.



      A much better approach is how factories really work. A constantly moving conveyor belt which moves the computers through one by one. At each location, the worker adds their respective component and then send each computer along individually to the next station. This would be equivalent to the following:



      computers_after_A = (add_motherboard(computer) for computer in bare_computers)
      computers_after_B = (add_CPU(computer) for computer in computer_after_A)
      computers_after_C = (add_RAM(computer) for computer in computer_after_B)


      With this approach we've replaced lists with generators. Now, computers are immediately available (instead of only available after everything finishes) and we don't need the space to build up 3 piles of 1000 computers.



      Hopefully, this motivates why we should use generators. I'll be using them below.



      There's no reason to use pandas to sort the counts from Counter. Counter has a most_common() method that will do this for you.



      You had the right idea with using translate to replace unwanted characters. But you don't need to build the dictionary every time. Instead using string.maketrans and saving this "translation dictionary" will avoid doing lots of extra work.



      remove_words_list should probably be called stop_list as this is the common parlance for words you want to exclude. Also, you should make this a frozenset so that word in stop_list is O(1) instead of an O(n) scan (what you currently do, which is really inefficient).



      Typically, we include code that we want to run in a main() function and run it only if __name__ == '__main__'. This allows other code to include this file (and potentially use an utility functions it defines) without having the main() run.



      With all that in mind, I'd refactor your code to something like this:



      import csv
      import string

      STOP_WORDS = frozenset(('at', 'of', 'a', 'and', 'in')) # ...
      EXCLUDE_CHARS = string.maketrans('', '', '.:,-%()[]?'')

      def main():
      with open('Article_titles.csv') as f: # consider accepting the filename as an argument (sys.argv)
      titles = (row['title'] for row in csv.DictReader(f, delimter='|'))
      words = (word.lower().translate(EXCLDUE_CHARS)
      for title in titles for word in title.split())
      interesting_words = (word for word in words if word not in STOP_WORDS)
      frequencies = Counter(interesting_words)

      print(frequencies.most_common())

      if __name__ == '__main__':
      main()


      Now remember how I mentioned above choosing the right tool for the job? It turns out that what you're trying to do (word extraction) is pretty tricky. Natural language has all sorts of irregularities, odd formatting, and inconsistencies. Luckily, there's a fantastic Python library which has a fairly good (complicated) implementation for handling all this for you: nltk. This is a very appropriate use of a library, because this task is hard and nltk has put lots of work into building a robust implementation. The home page has an example of what you need:



      >>> from nltk import word_tokenize
      >>> word_tokenize('When this thing hits 88 miles per hour')
      ['When', 'this', 'thing', 'hits', '88', 'miles', 'per', 'hour']


      I'll leave integrating nltk as an exercise for you, but it should only involve changing one line in my above code.






      share|improve this answer









      $endgroup$















        1












        1








        1





        $begingroup$

        Very good first post! There are a few small things to call out. First, refrain from using pandas and numpy unless it's absolutely necessary. Both are fantastic tools which excel in certain areas, but generally speaking many stock Python scripts can be just as performant, much more portable, and a lot more clear on their own. For example, stock Python is fantastically suited for counting the frequency of hashable items (in fact, you used it with Counter). But if I wanted to do QR decomposition, I would absolutely reach for numpy as it already has an implementation of this and that implementation will likely be much faster than anything I can write. Always be sure to use the right tool for the job. Generally speaking, the more dependencies a piece of code has, the harder it is to compose with other things (harder could be quantifying programmer effort here--as in, the effort to install dependencies/debug version issues/etc.). So for this reason, I'm going to remove numpy and pandas from my answer.




        #imports Counter, as we will need it later:
        from collections import Counter


        This is a useless comment. Of course that's what the following line does. And of course you're importing something because you need it. Don't just comment for the sake of commenting. Comment to explain the "why" not the "what."




        You can do your csv reading with stock python. You should be using with contexts here to handle properly closing files (especially if exceptions are raised). csv.DictReader will be useful here as it is an iterator that yields dicts for each row. This allows us to use a generator comprehension to build the list of titles. If you are unfamiliar with generators, you should [read up on them}(https://stackoverflow.com/q/1756096/568785). The benefit they give you is that they don't build up a big list in memory. They only produce values upon request. In your code you have a lot of list and string manipulation (see the list comprehension of resultwords, the join/split dance of total_words_string and querywords--which is unnecessary--,and huge_title_list). All of these points can be bottlenecks, because they require building up in memory the entire state to that point. Instead, using a generator allows you to lazily defer the work until you need it (in this case, when you use Counter).



        A good analogy for this is an assembly line. Imagine you had a factory building computers with 3 stops on the assembly line (we'll call them A, B, and C). At each stop, a worker adds one component to the computer (let's say motherboard, CPU, RAM). If we were to write this out using lists it would look like this:



        computers_after_A = [add_motherboard(computer) for computer in bare_computers]
        computers_after_B = [add_CPU(computer) for computer in computer_after_A]
        computers_after_C = [add_RAM(computer) for computer in computer_after_B]


        This looks innocent enough, but if you were to run your factory like this you'd have some real problems. Let's say the factory processes 1000 computers per day. The above code would be equivalent to the following:



        1. The worker at A adds motherboards to each incoming computer and then adds the finished computers to a big stack.

        2. Once worker A is done with all 1000 computers, the stack is pushed to worker B who takes one from each stack, adds the CPU and then makes a new stack of finished computers.

        3. Once worker B is done with all 1000 computers, the stack of 1000 computers is pushed to worker C, who adds the RAM and then adds the computers to the finally done stack.

        The problem with the above is that it is inefficient (worker B has no work until worker A completes all 1000 computers and worker C has no work until both workers A and B finish all 1000 computers) and requires you to have the room to store all 1000 computers at each location A, B, and C.



        A much better approach is how factories really work. A constantly moving conveyor belt which moves the computers through one by one. At each location, the worker adds their respective component and then send each computer along individually to the next station. This would be equivalent to the following:



        computers_after_A = (add_motherboard(computer) for computer in bare_computers)
        computers_after_B = (add_CPU(computer) for computer in computer_after_A)
        computers_after_C = (add_RAM(computer) for computer in computer_after_B)


        With this approach we've replaced lists with generators. Now, computers are immediately available (instead of only available after everything finishes) and we don't need the space to build up 3 piles of 1000 computers.



        Hopefully, this motivates why we should use generators. I'll be using them below.



        There's no reason to use pandas to sort the counts from Counter. Counter has a most_common() method that will do this for you.



        You had the right idea with using translate to replace unwanted characters. But you don't need to build the dictionary every time. Instead using string.maketrans and saving this "translation dictionary" will avoid doing lots of extra work.



        remove_words_list should probably be called stop_list as this is the common parlance for words you want to exclude. Also, you should make this a frozenset so that word in stop_list is O(1) instead of an O(n) scan (what you currently do, which is really inefficient).



        Typically, we include code that we want to run in a main() function and run it only if __name__ == '__main__'. This allows other code to include this file (and potentially use an utility functions it defines) without having the main() run.



        With all that in mind, I'd refactor your code to something like this:



        import csv
        import string

        STOP_WORDS = frozenset(('at', 'of', 'a', 'and', 'in')) # ...
        EXCLUDE_CHARS = string.maketrans('', '', '.:,-%()[]?'')

        def main():
        with open('Article_titles.csv') as f: # consider accepting the filename as an argument (sys.argv)
        titles = (row['title'] for row in csv.DictReader(f, delimter='|'))
        words = (word.lower().translate(EXCLDUE_CHARS)
        for title in titles for word in title.split())
        interesting_words = (word for word in words if word not in STOP_WORDS)
        frequencies = Counter(interesting_words)

        print(frequencies.most_common())

        if __name__ == '__main__':
        main()


        Now remember how I mentioned above choosing the right tool for the job? It turns out that what you're trying to do (word extraction) is pretty tricky. Natural language has all sorts of irregularities, odd formatting, and inconsistencies. Luckily, there's a fantastic Python library which has a fairly good (complicated) implementation for handling all this for you: nltk. This is a very appropriate use of a library, because this task is hard and nltk has put lots of work into building a robust implementation. The home page has an example of what you need:



        >>> from nltk import word_tokenize
        >>> word_tokenize('When this thing hits 88 miles per hour')
        ['When', 'this', 'thing', 'hits', '88', 'miles', 'per', 'hour']


        I'll leave integrating nltk as an exercise for you, but it should only involve changing one line in my above code.






        share|improve this answer









        $endgroup$



        Very good first post! There are a few small things to call out. First, refrain from using pandas and numpy unless it's absolutely necessary. Both are fantastic tools which excel in certain areas, but generally speaking many stock Python scripts can be just as performant, much more portable, and a lot more clear on their own. For example, stock Python is fantastically suited for counting the frequency of hashable items (in fact, you used it with Counter). But if I wanted to do QR decomposition, I would absolutely reach for numpy as it already has an implementation of this and that implementation will likely be much faster than anything I can write. Always be sure to use the right tool for the job. Generally speaking, the more dependencies a piece of code has, the harder it is to compose with other things (harder could be quantifying programmer effort here--as in, the effort to install dependencies/debug version issues/etc.). So for this reason, I'm going to remove numpy and pandas from my answer.




        #imports Counter, as we will need it later:
        from collections import Counter


        This is a useless comment. Of course that's what the following line does. And of course you're importing something because you need it. Don't just comment for the sake of commenting. Comment to explain the "why" not the "what."




        You can do your csv reading with stock python. You should be using with contexts here to handle properly closing files (especially if exceptions are raised). csv.DictReader will be useful here as it is an iterator that yields dicts for each row. This allows us to use a generator comprehension to build the list of titles. If you are unfamiliar with generators, you should [read up on them}(https://stackoverflow.com/q/1756096/568785). The benefit they give you is that they don't build up a big list in memory. They only produce values upon request. In your code you have a lot of list and string manipulation (see the list comprehension of resultwords, the join/split dance of total_words_string and querywords--which is unnecessary--,and huge_title_list). All of these points can be bottlenecks, because they require building up in memory the entire state to that point. Instead, using a generator allows you to lazily defer the work until you need it (in this case, when you use Counter).



        A good analogy for this is an assembly line. Imagine you had a factory building computers with 3 stops on the assembly line (we'll call them A, B, and C). At each stop, a worker adds one component to the computer (let's say motherboard, CPU, RAM). If we were to write this out using lists it would look like this:



        computers_after_A = [add_motherboard(computer) for computer in bare_computers]
        computers_after_B = [add_CPU(computer) for computer in computer_after_A]
        computers_after_C = [add_RAM(computer) for computer in computer_after_B]


        This looks innocent enough, but if you were to run your factory like this you'd have some real problems. Let's say the factory processes 1000 computers per day. The above code would be equivalent to the following:



        1. The worker at A adds motherboards to each incoming computer and then adds the finished computers to a big stack.

        2. Once worker A is done with all 1000 computers, the stack is pushed to worker B who takes one from each stack, adds the CPU and then makes a new stack of finished computers.

        3. Once worker B is done with all 1000 computers, the stack of 1000 computers is pushed to worker C, who adds the RAM and then adds the computers to the finally done stack.

        The problem with the above is that it is inefficient (worker B has no work until worker A completes all 1000 computers and worker C has no work until both workers A and B finish all 1000 computers) and requires you to have the room to store all 1000 computers at each location A, B, and C.



        A much better approach is how factories really work. A constantly moving conveyor belt which moves the computers through one by one. At each location, the worker adds their respective component and then send each computer along individually to the next station. This would be equivalent to the following:



        computers_after_A = (add_motherboard(computer) for computer in bare_computers)
        computers_after_B = (add_CPU(computer) for computer in computer_after_A)
        computers_after_C = (add_RAM(computer) for computer in computer_after_B)


        With this approach we've replaced lists with generators. Now, computers are immediately available (instead of only available after everything finishes) and we don't need the space to build up 3 piles of 1000 computers.



        Hopefully, this motivates why we should use generators. I'll be using them below.



        There's no reason to use pandas to sort the counts from Counter. Counter has a most_common() method that will do this for you.



        You had the right idea with using translate to replace unwanted characters. But you don't need to build the dictionary every time. Instead using string.maketrans and saving this "translation dictionary" will avoid doing lots of extra work.



        remove_words_list should probably be called stop_list as this is the common parlance for words you want to exclude. Also, you should make this a frozenset so that word in stop_list is O(1) instead of an O(n) scan (what you currently do, which is really inefficient).



        Typically, we include code that we want to run in a main() function and run it only if __name__ == '__main__'. This allows other code to include this file (and potentially use an utility functions it defines) without having the main() run.



        With all that in mind, I'd refactor your code to something like this:



        import csv
        import string

        STOP_WORDS = frozenset(('at', 'of', 'a', 'and', 'in')) # ...
        EXCLUDE_CHARS = string.maketrans('', '', '.:,-%()[]?'')

        def main():
        with open('Article_titles.csv') as f: # consider accepting the filename as an argument (sys.argv)
        titles = (row['title'] for row in csv.DictReader(f, delimter='|'))
        words = (word.lower().translate(EXCLDUE_CHARS)
        for title in titles for word in title.split())
        interesting_words = (word for word in words if word not in STOP_WORDS)
        frequencies = Counter(interesting_words)

        print(frequencies.most_common())

        if __name__ == '__main__':
        main()


        Now remember how I mentioned above choosing the right tool for the job? It turns out that what you're trying to do (word extraction) is pretty tricky. Natural language has all sorts of irregularities, odd formatting, and inconsistencies. Luckily, there's a fantastic Python library which has a fairly good (complicated) implementation for handling all this for you: nltk. This is a very appropriate use of a library, because this task is hard and nltk has put lots of work into building a robust implementation. The home page has an example of what you need:



        >>> from nltk import word_tokenize
        >>> word_tokenize('When this thing hits 88 miles per hour')
        ['When', 'this', 'thing', 'hits', '88', 'miles', 'per', 'hour']


        I'll leave integrating nltk as an exercise for you, but it should only involve changing one line in my above code.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered 54 mins ago









        Bailey ParkerBailey Parker

        2,1441014




        2,1441014




















            Jesper Mølgaard is a new contributor. Be nice, and check out our Code of Conduct.









            draft saved

            draft discarded


















            Jesper Mølgaard is a new contributor. Be nice, and check out our Code of Conduct.












            Jesper Mølgaard is a new contributor. Be nice, and check out our Code of Conduct.











            Jesper Mølgaard is a new contributor. Be nice, and check out our Code of Conduct.














            Thanks for contributing an answer to Code Review Stack Exchange!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            Use MathJax to format equations. MathJax reference.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f217334%2fcount-words-in-a-list-of-titles-with-some-cleanup%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            名間水力發電廠 目录 沿革 設施 鄰近設施 註釋 外部連結 导航菜单23°50′10″N 120°42′41″E / 23.83611°N 120.71139°E / 23.83611; 120.7113923°50′10″N 120°42′41″E / 23.83611°N 120.71139°E / 23.83611; 120.71139計畫概要原始内容臺灣第一座BOT 模式開發的水力發電廠-名間水力電廠名間水力發電廠 水利署首件BOT案原始内容《小檔案》名間電廠 首座BOT水力發電廠原始内容名間電廠BOT - 經濟部水利署中區水資源局

            格濟夫卡 參考資料 导航菜单51°3′40″N 34°2′21″E / 51.06111°N 34.03917°E / 51.06111; 34.03917ГезівкаПогода в селі 编辑或修订