Repository: sfu-db/dataprep
Issue: plot(df, x) error: word cloud requires at least 1 word

**Describe the bug**
Word cloud error

**To Reproduce**
```python
import pandas as pd
from dataprep.eda import plot
df = pd.DataFrame({"A": ["01-02", "01-02"]})
plot(df, "A", top_words=2)
```

**Expected behavior**
Produce the word cloud with no error

**Desktop (please complete the following information):**
 - OS: MacOS
 - Browser: Chrome
 - Platform: Jupyter Notebook
 - Platform Version: 6.0.3
 - Python Version: 3.8.3
 - DataPrep Version: 0.2.6

**Additional context**
``` python
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-43-f8a3846cc211> in <module>
      1 df = pd.DataFrame({"A": ["01-02", "01-02"]})
----> 2 plot(df, "A", top_words=2)

~/dataprep/dataprep/eda/basic/__init__.py in plot(df, x, y, z, bins, ngroups, largest, nsubgroups, timeunit, agg, sample_size, value_range, yscale, tile_size, dtype, top_words, stopword, lemmatize, stem)
    163         stem=stem,
    164     )
--> 165     figure = render(intermediate, yscale=yscale, tile_size=tile_size)
    166 
    167     return Report(figure)

~/dataprep/dataprep/eda/basic/render.py in render(itmdt, yscale, tile_size, plot_width_sml, plot_height_sml, plot_width_lrg, plot_height_lrg, plot_width_wide)
   1684         visual_elem = render_basic(itmdt, yscale, plot_width_sml, plot_height_sml)
   1685     elif itmdt.visual_type == "categorical_column":
-> 1686         visual_elem = render_cat(itmdt, yscale, plot_width_lrg, plot_height_lrg)
   1687     elif itmdt.visual_type == "numerical_column":
   1688         visual_elem = render_num(itmdt, yscale, plot_width_lrg, plot_height_lrg)

~/dataprep/dataprep/eda/basic/render.py in render_cat(itmdt, yscale, plot_width, plot_height)
   1393     tabs.append(pie_viz(df, itmdt["col"], miss_pct, plot_width, plot_height))
   1394     freq_tuple = itmdt["word_cloud"]
-> 1395     word_cloud = wordcloud_viz(freq_tuple, plot_width, plot_height)
   1396     tabs.append(Panel(child=row(word_cloud), title="word cloud"))
   1397     wordfreq = wordfreq_viz(freq_tuple, plot_width, plot_height, True)

~/dataprep/dataprep/eda/basic/render.py in wordcloud_viz(freq_tuple, plot_width, plot_height)
    228     )
    229     top_freq = freq_tuple[1]
--> 230     wordcloud.generate_from_frequencies(dict(top_freq))
    231     wcimg = wordcloud.to_array().astype(np.uint8)
    232     alpha = np.full([*wcimg.shape[:2], 1], 255, dtype=np.uint8)

~/dataprep/.venv/lib/python3.8/site-packages/wordcloud/wordcloud.py in generate_from_frequencies(self, frequencies, max_font_size)
    401         frequencies = sorted(frequencies.items(), key=itemgetter(1), reverse=True)
    402         if len(frequencies) <= 0:
--> 403             raise ValueError("We need at least 1 word to plot a word cloud, "
    404                              "got %d." % len(frequencies))
    405         frequencies = frequencies[:self.max_words]

ValueError: We need at least 1 word to plot a word cloud, got 0.
```

Changed files:
dataprep/eda/basic/compute.py, dataprep/eda/basic/render.py

diff --git a/dataprep/eda/basic/compute.py b/dataprep/eda/basic/compute.py
index f3561489..3d4e6aea 100644
--- a/dataprep/eda/basic/compute.py
+++ b/dataprep/eda/basic/compute.py
@@ -297,13 +297,17 @@ def compute_univariate(
 
     col_dtype = detect_dtype(df[x], dtype)
     if is_dtype(col_dtype, Nominal()):
-        # data for bar and pie charts
         data_cat: List[Any] = []
-        data_cat.append(dask.delayed(calc_bar_pie)(df[x], ngroups, largest))
+        # reset index for calculating quantile stats
+        df = df.reset_index()
         # stats
         data_cat.append(dask.delayed(calc_stats_cat)(df[x]))
-        data, statsdata_cat = dask.compute(*data_cat)
-
+        # drop nan and empty spaces for plots
+        df[x].replace("", np.nan)
+        df = df.dropna(subset=[x])
+        # data for bar and pie charts
+        data_cat.append(dask.delayed(calc_bar_pie)(df[x], ngroups, largest))
+        statsdata_cat, data = dask.compute(*data_cat)
         # wordcloud and word frequencies
         word_cloud = cal_word_freq(df, x, top_words, stopword, lemmatize, stem)
         # length_distribution
@@ -858,7 +862,7 @@ def clean_text(
     lemmatizer = WordNetLemmatizer()
     porter = PorterStemmer()
     for key in freq_copy.keys():
-        if stopword and non_single_word >= top_words:  # type: ignore
+        if stopword and non_single_word > top_words:  # type: ignore
             if key in english_stopwords.english_stopwords or len(key) <= 2:
                 del freqdist[key]
         if lemmatize:
@@ -1351,28 +1355,12 @@ def calc_stats_cat(
         "Memory Size": srs.memory_usage(),
     }
     srs = srs.astype("str")
-    # length stats
-    length = srs.str.len()
-    length_dict = {
-        "mean": length.mean(),
-        "median": length.median(),
-        "minimum": length.min(),
-        "maximum": length.max(),
-    }
     # quantile stats
-    max_lbl_len = 13
+    max_lbl_len = 25
     quantile_dict = {}
     for label, centile in zip(
-        (
-            "Minimum",
-            "5-th Percentile",
-            "Q1",
-            "Median",
-            "Q3",
-            "95-th Percentile",
-            "Maximum",
-        ),
-        (0, 0.05, 0.25, 0.5, 0.75, 0.95, 1),
+        ("1st Row", "25% Row", "50% Row", "75% Row", "Last Row",),
+        (0, 0.25, 0.5, 0.75, 1),
     ):
         if round(len(srs) * centile) == 0:
             element = srs[round(len(srs) * centile)]
@@ -1387,9 +1375,19 @@ def calc_stats_cat(
             else:
                 quantile_dict[label] = element
 
+    srs = srs.dropna()
+    # length stats
+    length = srs.str.len()
+    length_dict = {
+        "Mean": length.mean(),
+        "Standard Deviation": length.std(),
+        "Median": length.median(),
+        "Minimum": length.min(),
+        "Maximum": length.max(),
+    }
     # letter stats
     letter_dict = {
-        "count": srs.str.count(r"[a-zA-Z]").sum(),
+        "Count": srs.str.count(r"[a-zA-Z]").sum(),
         "Lowercase Letter": srs.str.count(r"[a-z]").sum(),
         "Space Separator": srs.str.count(r"[ ]").sum(),
         "Uppercase Letter": srs.str.count(r"[A-Z]").sum(),
diff --git a/dataprep/eda/basic/render.py b/dataprep/eda/basic/render.py
index f4413aa1..848810bb 100644
--- a/dataprep/eda/basic/render.py
+++ b/dataprep/eda/basic/render.py
@@ -2,43 +2,43 @@
 This module implements the visualization for the plot(df) function.
 """  # pylint: disable=too-many-lines
 from math import pi
-from typing import Any, Dict, List, Optional, Tuple, Union
 from pathlib import Path
-from wordcloud import WordCloud
-from PIL import Image
+from typing import Any, Dict, List, Optional, Tuple, Union
 
 import numpy as np
 import pandas as pd
+from bokeh.events import ButtonClick
+from bokeh.layouts import column, row
 from bokeh.models import (
+    BasicTicker,
     Box,
+    Button,
+    ColorBar,
+    ColumnDataSource,
+    CustomJS,
+    CustomJSHover,
+    Div,
+    FactorRange,
     FuncTickFormatter,
-    PrintfTickFormatter,
-    BasicTicker,
     HoverTool,
     LayoutDOM,
     Legend,
     LegendItem,
+    LinearColorMapper,
     Panel,
+    PrintfTickFormatter,
     Tabs,
-    ColumnDataSource,
-    LinearColorMapper,
-    ColorBar,
-    FactorRange,
-    CustomJSHover,
-    Div,
-    CustomJS,
-    Button,
 )
-from bokeh.layouts import column, row
-from bokeh.plotting import Figure, gridplot, figure
+from bokeh.palettes import Pastel1, viridis  # pylint: disable=E0611 # type: ignore
+from bokeh.plotting import Figure, figure, gridplot
 from bokeh.transform import cumsum, linear_cmap, transform
 from bokeh.util.hex import hexbin
-from bokeh.palettes import viridis, Pastel1  # pylint: disable=E0611 # type: ignore
-from bokeh.events import ButtonClick
+from PIL import Image
+from wordcloud import WordCloud
 
+from ..dtypes import Continuous, DateTime, Nominal, is_dtype
 from ..intermediate import Intermediate
-from ..dtypes import is_dtype, Nominal, Continuous, DateTime
-from ..palette import PALETTE, BIPALETTE
+from ..palette import BIPALETTE, PALETTE
 
 __all__ = ["render"]
 
@@ -343,9 +343,11 @@ def pie_viz(
     )
     color_list = PALETTE * (len(df) // len(PALETTE) + 1)
     df["colour"] = color_list[0 : len(df)]
-    if df.iloc[-1]["cnt"] == 0:  # no "Others" group
-        df = df[:-1]
     df["col"] = df["col"].map(lambda x: x[0:13] + "..." if len(x) > 13 else x)
+
+    if df.iloc[-1]["cnt"] == 0:  # no "Others" group
+        df = df.iloc[:-1]
+
     pie = fig.wedge(
         x=0,
         y=1,
@@ -1249,38 +1251,39 @@ def stats_viz_cat(
     ov_content = f"""
     <div style="grid-area: a;">
         <h3 style="text-align: center;">Overview</h3>
-        <table style="width: 100%; table-layout: auto;">
+        <table style="width: 100%; table-layout: auto; font-size:11px;">
             <tbody>{ov_content}</tbody>
         </table>
     </div>
     """
-    lens_content = f"""
+    qs_content = f"""
     <div style="grid-area: b;">
-        <h3 style="text-align: center;">Length</h3>
+        <h3 style="text-align: center;">Sample</h3>
         <table style="width: 100%; table-layout: auto; font-size:11px;">
-            <tbody>{lens_content}</tbody>
+            <tbody>{qs_content}</tbody>
         </table>
     </div>
     """
-    qs_content = f"""
+    ls_content = f"""
     <div style="grid-area: c;">
-        <h3 style="text-align: center;margin-top: -10px;">Quantile Statistics</h3>
+        <h3 style="text-align: center;">Letter</h3>
         <table style="width: 100%; table-layout: auto; font-size:11px;">
-            <tbody>{qs_content}</tbody>
+            <tbody>{ls_content}</tbody>
         </table>
     </div>
     """
-    ls_content = f"""
+    lens_content = f"""
     <div style="grid-area: d;">
-        <h3 style="text-align: center;margin-top: -10px;">Letter</h3>
+        <h3 style="text-align: center;">Length</h3>
         <table style="width: 100%; table-layout: auto; font-size:11px;">
-            <tbody>{ls_content}</tbody>
+            <tbody>{lens_content}</tbody>
         </table>
     </div>
     """
+
     container = f"""<div style="display: grid;grid-template-columns: 1fr 1fr;grid-template-rows: 1fr 1fr;gap: 1px 1px;
                 grid-template-areas:\\'a b\\' \\'c d\\';">
-                {ov_content}{lens_content}{qs_content}{ls_content}</div>"""
+                {ov_content}{qs_content}{ls_content}{lens_content}</div>"""
 
     div = Div(
         text=container, width=plot_width, height=plot_height, style={"width": "100%"}
@@ -1398,16 +1401,18 @@ def render_cat(
     tabs.append(Panel(child=row(fig), title="bar chart"))
     tabs.append(pie_viz(df, itmdt["col"], miss_pct, plot_width, plot_height))
     freq_tuple = itmdt["word_cloud"]
-    word_cloud = wordcloud_viz(freq_tuple, plot_width, plot_height)
-    tabs.append(Panel(child=row(word_cloud), title="word cloud"))
-    wordfreq = wordfreq_viz(freq_tuple, plot_width, plot_height, True)
-    tabs.append(Panel(child=row(wordfreq), title="words frequency"))
+    if freq_tuple[0] != 0:
+        word_cloud = wordcloud_viz(freq_tuple, plot_width, plot_height)
+        tabs.append(Panel(child=row(word_cloud), title="word cloud"))
+        wordfreq = wordfreq_viz(freq_tuple, plot_width, plot_height, True)
+        tabs.append(Panel(child=row(wordfreq), title="words frequency"))
     df, miss_pct = itmdt["length_dist"]
     length_dist = hist_viz(
         df, miss_pct, "length", yscale, plot_width, plot_height, True
     )
     tabs.append(Panel(child=row(length_dist), title="length"))
     tabs = Tabs(tabs=tabs)
+
     return tabs
 
 
@@ -1705,4 +1710,5 @@ def render(
         visual_elem = render_dt_cat(itmdt, yscale, plot_width_wide, plot_height_lrg)
     elif itmdt.visual_type == "dt_cat_num_cols":
         visual_elem = render_dt_num_cat(itmdt, yscale, plot_width_wide, plot_height_lrg)
+
     return visual_elem