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;
$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
....
...
python strings csv pandas
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$
add a comment |
$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
....
...
python strings csv pandas
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
add a comment |
$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
....
...
python strings csv pandas
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
python strings csv pandas
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.
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
add a comment |
$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
add a comment |
1 Answer
1
active
oldest
votes
$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:
- The worker at A adds motherboards to each incoming computer and then adds the finished computers to a big stack.
- 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.
- 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.
$endgroup$
add a comment |
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
var $window = $(window),
onScroll = function(e)
var $elem = $('.new-login-left'),
docViewTop = $window.scrollTop(),
docViewBottom = docViewTop + $window.height(),
elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height();
if ((docViewTop elemBottom))
StackExchange.using('gps', function() StackExchange.gps.track('embedded_signup_form.view', location: 'question_page' ); );
$window.unbind('scroll', onScroll);
;
$window.on('scroll', onScroll);
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
$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:
- The worker at A adds motherboards to each incoming computer and then adds the finished computers to a big stack.
- 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.
- 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.
$endgroup$
add a comment |
$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:
- The worker at A adds motherboards to each incoming computer and then adds the finished computers to a big stack.
- 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.
- 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.
$endgroup$
add a comment |
$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:
- The worker at A adds motherboards to each incoming computer and then adds the finished computers to a big stack.
- 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.
- 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.
$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:
- The worker at A adds motherboards to each incoming computer and then adds the finished computers to a big stack.
- 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.
- 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.
answered 54 mins ago
Bailey ParkerBailey Parker
2,1441014
2,1441014
add a comment |
add a comment |
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.
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
var $window = $(window),
onScroll = function(e)
var $elem = $('.new-login-left'),
docViewTop = $window.scrollTop(),
docViewBottom = docViewTop + $window.height(),
elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height();
if ((docViewTop elemBottom))
StackExchange.using('gps', function() StackExchange.gps.track('embedded_signup_form.view', location: 'question_page' ); );
$window.unbind('scroll', onScroll);
;
$window.on('scroll', onScroll);
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
var $window = $(window),
onScroll = function(e)
var $elem = $('.new-login-left'),
docViewTop = $window.scrollTop(),
docViewBottom = docViewTop + $window.height(),
elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height();
if ((docViewTop elemBottom))
StackExchange.using('gps', function() StackExchange.gps.track('embedded_signup_form.view', location: 'question_page' ); );
$window.unbind('scroll', onScroll);
;
$window.on('scroll', onScroll);
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
var $window = $(window),
onScroll = function(e)
var $elem = $('.new-login-left'),
docViewTop = $window.scrollTop(),
docViewBottom = docViewTop + $window.height(),
elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height();
if ((docViewTop elemBottom))
StackExchange.using('gps', function() StackExchange.gps.track('embedded_signup_form.view', location: 'question_page' ); );
$window.unbind('scroll', onScroll);
;
$window.on('scroll', onScroll);
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
var $window = $(window),
onScroll = function(e)
var $elem = $('.new-login-left'),
docViewTop = $window.scrollTop(),
docViewBottom = docViewTop + $window.height(),
elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height();
if ((docViewTop elemBottom))
StackExchange.using('gps', function() StackExchange.gps.track('embedded_signup_form.view', location: 'question_page' ); );
$window.unbind('scroll', onScroll);
;
$window.on('scroll', onScroll);
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
$begingroup$
Welcome to Code Review! Good job on your first question. I hope you get great answers.
$endgroup$
– Alex
2 hours ago