Repository: amueller/word_cloud

Issue:
Some text distributions yield duplicate words in image
As a workaround for #226 I tried generating text according to the desired distribution: [food.txt](https://github.com/amueller/word_cloud/files/858596/food.txt)

Note in particular there are few word types but relatively high number of tokens per type compared to natural language.  Tokens of each word type are also grouped.  (My guess would be one or both of these properties are causing the issue.)

However, `wordcloud_cli.py` (from master) on this generated text file, with default options, yielded a word cloud with each word duplicated conspicuously: ![food](https://cloud.githubusercontent.com/assets/457238/24154620/126cef40-0e28-11e7-8946-4e6284ceffae.png)


Discussion:
amueller: This PR only adds the flag, but keeps the default as "true", right?
That seems like a good solution to me. The program is more "here is some text, make a word-cloud out of it". And separating two words that always appear together isn't that intuitive to me.
cjmay: Okay, I'll change it: the default behavior in the CLI would be switched in the current PR.
amueller: thanks :)

Patch:
diff --git a/wordcloud/wordcloud.py b/wordcloud/wordcloud.py
index e63d922..ae5d107 100644
--- a/wordcloud/wordcloud.py
+++ b/wordcloud/wordcloud.py
@@ -348,11 +348,7 @@ class WordCloud(object):
         """
         # make sure frequencies are sorted and normalized
         frequencies = sorted(frequencies.items(), key=item1, reverse=True)
-        if len(frequencies) <= 0:
-            raise ValueError("We need at least 1 word to plot a word cloud, "
-                             "got %d." % len(frequencies))
         frequencies = frequencies[:self.max_words]
-
         # largest entry will be 1
         max_frequency = float(frequencies[0][1])
 
@@ -364,6 +360,10 @@ class WordCloud(object):
         else:
             random_state = Random()
 
+        if len(frequencies) <= 0:
+            print("We need at least 1 word to plot a word cloud, got %d."
+                  % len(frequencies))
+
         if self.mask is not None:
             mask = self.mask
             width = mask.shape[1]
diff --git a/wordcloud/wordcloud_cli.py b/wordcloud/wordcloud_cli.py
index 2a54deb..cc105fe 100644
--- a/wordcloud/wordcloud_cli.py
+++ b/wordcloud/wordcloud_cli.py
@@ -16,7 +16,8 @@ def main(args):
     wordcloud = wc.WordCloud(stopwords=args.stopwords, mask=args.mask,
         width=args.width, height=args.height, font_path=args.font_path,
         margin=args.margin, relative_scaling=args.relative_scaling,
-        color_func=args.color_func, background_color=args.background_color).generate(args.text)
+        color_func=args.color_func, background_color=args.background_color,
+        collocations=args.collocations).generate(args.text)
     image = wordcloud.to_image()
 
     with args.imagefile:
@@ -51,6 +52,8 @@ def parse_args(arguments):
         help='use given color as coloring for the image - accepts any value from PIL.ImageColor.getcolor')
     parser.add_argument('--background', metavar='color', default='black', type=str, dest='background_color',
         help='use given color as background color for the image - accepts any value from PIL.ImageColor.getcolor')
+    parser.add_argument('--no_collocations', action='store_true',
+        help='do not add collocations (bigrams) to word cloud (default: add unigrams and bigrams)')
     args = parser.parse_args(arguments)
 
     if args.colormask and args.color:
@@ -74,6 +77,8 @@ def parse_args(arguments):
     if args.color:
         color_func = wc.get_single_color_func(args.color)
 
+    args.collocations = not args.no_collocations
+
     args.color_func = color_func
     return args
 


Test patch:
diff --git a/test/test_wordcloud.py b/test/test_wordcloud.py
index 2d2662b..ee91f2e 100644
--- a/test/test_wordcloud.py
+++ b/test/test_wordcloud.py
@@ -76,16 +76,6 @@ def test_multiple_s():
     assert_in("flosss", wc.words_)
 
 
-def test_empty_text():
-    # test originally empty text raises an exception
-    wc = WordCloud(stopwords=[])
-    assert_raises(ValueError, wc.generate, '')
-
-    # test empty-after-filtering text raises an exception
-    wc = WordCloud(stopwords=['a', 'b'])
-    assert_raises(ValueError, wc.generate, 'a b a')
-
-
 def test_default():
     # test that default word cloud creation and conversions work
     wc = WordCloud(max_words=50)
diff --git a/test/test_wordcloud_cli.py b/test/test_wordcloud_cli.py
index bdfef88..d350d81 100644
--- a/test/test_wordcloud_cli.py
+++ b/test/test_wordcloud_cli.py
@@ -17,7 +17,10 @@ ARGUMENT_SPEC_TYPED = [
     ArgOption(cli_name='width', init_name='width', pass_value=13, fail_value=1.),
     ArgOption(cli_name='height', init_name='height', pass_value=15, fail_value=1.),
     ArgOption(cli_name='margin', init_name='margin', pass_value=17, fail_value=1.),
-    ArgOption(cli_name='relative_scaling', init_name='relative_scaling', pass_value=1, fail_value='c')
+    ArgOption(cli_name='relative_scaling', init_name='relative_scaling', pass_value=1, fail_value='c'),
+]
+ARGUMENT_SPEC_UNARY = [
+    ArgOption(cli_name='no_collocations', init_name='collocations', pass_value=True, fail_value=1)
 ]
 ARGUMENT_SPEC_REMAINING = [
     ArgOption(cli_name='stopwords', init_name='stopwords', pass_value=temp.name, fail_value=None),
@@ -26,10 +29,12 @@ ARGUMENT_SPEC_REMAINING = [
     ArgOption(cli_name='color', init_name='color_func', pass_value='red', fail_value=None),
     ArgOption(cli_name='background', init_name='background_color', pass_value='grey', fail_value=None)
 ]
+ARGUMENT_CLI_NAMES_UNARY = [arg_opt.cli_name for arg_opt in ARGUMENT_SPEC_UNARY]
 
 def all_arguments():
     arguments = []
     arguments.extend(ARGUMENT_SPEC_TYPED)
+    arguments.extend(ARGUMENT_SPEC_UNARY)
     arguments.extend(ARGUMENT_SPEC_REMAINING)
     return arguments
 
@@ -56,6 +61,13 @@ def check_argument(name, result_name, value):
     assert_in(result_name, vars(args))
 
 
+def check_argument_unary(name, result_name):
+    text = NamedTemporaryFile()
+
+    args = cli.parse_args(['--text', text.name, '--' + name])
+    assert_in(result_name, vars(args))
+
+
 def check_argument_type(name, value):
     text = NamedTemporaryFile()
 
@@ -71,7 +83,9 @@ def check_argument_type(name, value):
 
 def test_parse_args_are_passed_along():
     for option in all_arguments():
-        if option.cli_name != 'mask':
+        if option.cli_name in ARGUMENT_CLI_NAMES_UNARY:
+            yield check_argument_unary, option.cli_name, option.init_name
+        elif option.cli_name != 'mask':
             yield check_argument, option.cli_name, option.init_name, option.pass_value