Repository: nltk/nltk

Issue:
NoneType() < bool() error when doing pretty format of nltk decision tree classifier in python3
<classifier>.pretty_format() works fine with Python2. But this is the error I get with python3:
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-629-dc64618292e3> in <module>()
----> 1 classifier.pretty_format()

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/nltk/classify/decisiontree.py in pretty_format(self, width, prefix, depth)
     84             return '%s%s %s\n' % (prefix, '.'*n, self._label)
     85         s = ''
---> 86         for i, (fval, result) in enumerate(sorted(self._decisions.items())):
     87             hdr = '%s%s=%s? ' % (prefix, self._fname, fval)
     88             n = width-15-len(hdr)

TypeError: unorderable types: NoneType() < bool()


Discussion:
This issue also applies to .pseudocode()  

It is because there is a None which cannot be sorted in Python 3.
Line 86 
`        for i, (fval, result) in enumerate(sorted(self._decisions.items())):`

and Line 108
`        for (fval, result) in sorted(self._decisions.items()):`

in classify/decisiontree.py

If I understood better why None gets assigned as a key on a node I could submit a patch to address this.
Why there might be a None as a node:
You might have features that don't apply (e.g. 'next_word_in_a_sentence', when at the end of a sentence).

Similarly, .pretty_format() and .pseudocode() also crash w/ TypeError when you have features with boolean values (e.g. 'Is there an adjective in this sentence?' - True/False?)

From what I see, there is not really a need to sort decisions in a special order, so one quick solution might be removing the sorting process. Or just casting the non-string values to strings to keep printing feature values in alphabetic order (possibly marking them as non-strings?).


Resolving patch:
diff --git a/nltk/classify/decisiontree.py b/nltk/classify/decisiontree.py
--- a/nltk/classify/decisiontree.py
+++ b/nltk/classify/decisiontree.py
@@ -79,10 +79,14 @@ def pretty_format(self, width=70, prefix="", depth=4):
         # [xx] display default!!
         if self._fname is None:
             n = width - len(prefix) - 15
-            return "{0}{1} {2}\n".format(prefix, "." * n, self._label)
-        s = ""
-        for i, (fval, result) in enumerate(sorted(self._decisions.items())):
-            hdr = "{0}{1}={2}? ".format(prefix, self._fname, fval)
+            return '{0}{1} {2}\n'.format(prefix, '.' * n, self._label)
+        s = ''
+        for i, (fval, result) in enumerate(sorted(self._decisions.items(), 
+                                                  key=lambda item: 
+                                                  (item[0] in [None, False, True], str(item[0]).lower())
+                                                 )
+                                          ):
+            hdr = '{0}{1}={2}? '.format(prefix, self._fname, fval)
             n = width - 15 - len(hdr)
             s += "{0}{1} {2}\n".format(hdr, "." * (n), result._label)
             if result._fname is not None and depth > 1:
@@ -102,9 +106,12 @@ def pseudocode(self, prefix="", depth=4):
         """
         if self._fname is None:
             return "{0}return {1!r}\n".format(prefix, self._label)
-        s = ""
-        for (fval, result) in sorted(self._decisions.items()):
-            s += "{0}if {1} == {2!r}: ".format(prefix, self._fname, fval)
+        s = ''
+        for (fval, result) in sorted(self._decisions.items(),
+                                    key=lambda item: 
+                                     (item[0] in [None, False, True], str(item[0]).lower())
+                                    ):
+            s += '{0}if {1} == {2!r}: '.format(prefix, self._fname, fval)
             if result._fname is not None and depth > 1:
                 s += "\n" + result.pseudocode(prefix + "  ", depth - 1)
             else:
@@ -339,7 +346,7 @@ def demo():
     classifier = names_demo(
         f, binary_names_demo_features  # DecisionTreeClassifier.train,
     )
-    print(classifier.pp(depth=7))
+    print(classifier.pretty_format(depth=7))
     print(classifier.pseudocode(depth=7))
 
 
diff --git a/nltk/classify/naivebayes.py b/nltk/classify/naivebayes.py
--- a/nltk/classify/naivebayes.py
+++ b/nltk/classify/naivebayes.py
@@ -133,7 +133,8 @@ def labelprob(l):
 
             labels = sorted(
                 [l for l in self._labels if fval in cpdist[l, fname].samples()],
-                key=labelprob,
+                key=lambda element: (-labelprob(element), element),
+                reverse=True
             )
             if len(labels) == 1:
                 continue
@@ -185,7 +186,8 @@ def most_informative_features(self, n=100):
             # Convert features to a list, & sort it by how informative
             # features are.
             self._most_informative_features = sorted(
-                features, key=lambda feature_: minprob[feature_] / maxprob[feature_]
+                features, key=lambda feature_: (minprob[feature_] / maxprob[feature_], feature_[0],
+                                                feature_[1] in [None, False, True], str(feature_[1]).lower())
             )
         return self._most_informative_features[:n]
 


Test patch:
diff --git a/nltk/test/classify.doctest b/nltk/test/classify.doctest
--- a/nltk/test/classify.doctest
+++ b/nltk/test/classify.doctest
@@ -49,6 +49,7 @@ haven't done this yet for all tests).
     ...     (dict(a=0,b=1,c=0), 'x'),
     ...     (dict(a=0,b=0,c=0), 'x'),
     ...     (dict(a=0,b=1,c=1), 'y'),
+    ...     (dict(a=None,b=1,c=0), 'x'),
     ...     ]
     >>> test = [
     ...     (dict(a=1,b=0,c=1)), # unseen
@@ -66,24 +67,24 @@ Test the Naive Bayes classifier:
     ['y', 'x', 'y', 'x']
     >>> for pdist in classifier.prob_classify_many(test):
     ...     print('%.4f %.4f' % (pdist.prob('x'), pdist.prob('y')))
-    0.3203 0.6797
-    0.5857 0.4143
-    0.3792 0.6208
-    0.6470 0.3530
+    0.2500 0.7500
+    0.5833 0.4167
+    0.3571 0.6429
+    0.7000 0.3000
     >>> classifier.show_most_informative_features()
     Most Informative Features
-                           c = 0                   x : y      =      2.0 : 1.0
-                           c = 1                   y : x      =      1.5 : 1.0
-                           a = 1                   y : x      =      1.4 : 1.0
-                           b = 0                   x : y      =      1.2 : 1.0
-                           a = 0                   x : y      =      1.2 : 1.0
-                           b = 1                   y : x      =      1.1 : 1.0
+                           c = 0                   x : y      =      2.3 : 1.0
+                           c = 1                   y : x      =      1.8 : 1.0
+                           a = 1                   y : x      =      1.7 : 1.0
+                           a = 0                   x : y      =      1.0 : 1.0
+                           b = 0                   x : y      =      1.0 : 1.0
+                           b = 1                   x : y      =      1.0 : 1.0
 
-Test the Decision Tree classifier:
+Test the Decision Tree classifier (without None):
 
     >>> classifier = nltk.classify.DecisionTreeClassifier.train(
-    ...     train, entropy_cutoff=0,
-    ...                                                support_cutoff=0)
+    ...     train[:-1], entropy_cutoff=0,
+    ...     support_cutoff=0)
     >>> sorted(classifier.labels())
     ['x', 'y']
     >>> print(classifier)
@@ -99,6 +100,23 @@ Test the Decision Tree classifier:
     Traceback (most recent call last):
       . . .
     NotImplementedError
+    
+    
+Test the Decision Tree classifier (with None):
+
+    >>> classifier = nltk.classify.DecisionTreeClassifier.train(
+    ...     train, entropy_cutoff=0,
+    ...     support_cutoff=0)
+    >>> sorted(classifier.labels())
+    ['x', 'y']
+    >>> print(classifier)
+    c=0? .................................................. x
+      a=0? ................................................ x
+      a=1? ................................................ y
+      a=None? ............................................. x
+    c=1? .................................................. y
+    <BLANKLINE>
+
 
 Test SklearnClassifier, which requires the scikit-learn package.