nltk 3.2__zip → 3.3__zip

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. {nltk-3.2 → nltk-3.3}/INSTALL.txt +6 -6
  2. {nltk-3.2 → nltk-3.3}/LICENSE.txt +13 -13
  3. {nltk-3.2 → nltk-3.3}/MANIFEST.in +6 -6
  4. {nltk-3.2/nltk.egg-info → nltk-3.3}/PKG-INFO +13 -5
  5. nltk-3.2/README.txt → nltk-3.3/README.md +49 -48
  6. nltk-3.3/nltk/VERSION +1 -0
  7. {nltk-3.2 → nltk-3.3}/nltk/__init__.py +185 -185
  8. {nltk-3.2 → nltk-3.3}/nltk/app/__init__.py +52 -53
  9. {nltk-3.2 → nltk-3.3}/nltk/app/chartparser_app.py +2276 -2276
  10. {nltk-3.2 → nltk-3.3}/nltk/app/chunkparser_app.py +1262 -1264
  11. {nltk-3.2 → nltk-3.3}/nltk/app/collocations_app.py +347 -350
  12. {nltk-3.2 → nltk-3.3}/nltk/app/concordance_app.py +568 -570
  13. {nltk-3.2 → nltk-3.3}/nltk/app/nemo_app.py +156 -155
  14. {nltk-3.2 → nltk-3.3}/nltk/app/rdparser_app.py +892 -894
  15. {nltk-3.2 → nltk-3.3}/nltk/app/srparser_app.py +809 -809
  16. {nltk-3.2 → nltk-3.3}/nltk/app/wordfreq_app.py +32 -32
  17. {nltk-3.2 → nltk-3.3}/nltk/app/wordnet_app.py +968 -968
  18. {nltk-3.2 → nltk-3.3}/nltk/book.py +91 -90
  19. {nltk-3.2 → nltk-3.3}/nltk/ccg/__init__.py +22 -22
  20. {nltk-3.2 → nltk-3.3}/nltk/ccg/api.py +359 -335
  21. {nltk-3.2 → nltk-3.3}/nltk/ccg/chart.py +394 -361
  22. {nltk-3.2 → nltk-3.3}/nltk/ccg/combinator.py +343 -312
  23. {nltk-3.2 → nltk-3.3}/nltk/ccg/lexicon.py +328 -290
  24. nltk-3.3/nltk/ccg/logic.py +46 -0
  25. {nltk-3.2 → nltk-3.3}/nltk/chat/__init__.py +49 -49
  26. {nltk-3.2 → nltk-3.3}/nltk/chat/eliza.py +244 -244
  27. {nltk-3.2 → nltk-3.3}/nltk/chat/iesha.py +140 -140
  28. {nltk-3.2 → nltk-3.3}/nltk/chat/rude.py +92 -92
  29. {nltk-3.2 → nltk-3.3}/nltk/chat/suntsu.py +117 -117
  30. {nltk-3.2 → nltk-3.3}/nltk/chat/util.py +122 -120
  31. {nltk-3.2 → nltk-3.3}/nltk/chat/zen.py +282 -282
  32. {nltk-3.2 → nltk-3.3}/nltk/chunk/__init__.py +190 -190
  33. {nltk-3.2 → nltk-3.3}/nltk/chunk/api.py +51 -51
  34. {nltk-3.2 → nltk-3.3}/nltk/chunk/named_entity.py +331 -331
  35. {nltk-3.2 → nltk-3.3}/nltk/chunk/regexp.py +1390 -1384
  36. {nltk-3.2 → nltk-3.3}/nltk/chunk/util.py +600 -600
  37. {nltk-3.2 → nltk-3.3}/nltk/classify/__init__.py +98 -98
  38. {nltk-3.2 → nltk-3.3}/nltk/classify/api.py +193 -193
  39. {nltk-3.2 → nltk-3.3}/nltk/classify/decisiontree.py +295 -295
  40. {nltk-3.2 → nltk-3.3}/nltk/classify/maxent.py +1510 -1492
  41. {nltk-3.2 → nltk-3.3}/nltk/classify/megam.py +179 -178
  42. {nltk-3.2 → nltk-3.3}/nltk/classify/naivebayes.py +245 -242
  43. {nltk-3.2 → nltk-3.3}/nltk/classify/positivenaivebayes.py +170 -170
  44. {nltk-3.2 → nltk-3.3}/nltk/classify/rte_classify.py +160 -191
  45. {nltk-3.2 → nltk-3.3}/nltk/classify/scikitlearn.py +153 -151
  46. {nltk-3.2 → nltk-3.3}/nltk/classify/senna.py +183 -181
  47. {nltk-3.2 → nltk-3.3}/nltk/classify/svm.py +15 -15
  48. {nltk-3.2 → nltk-3.3}/nltk/classify/tadm.py +113 -112
  49. {nltk-3.2 → nltk-3.3}/nltk/classify/textcat.py +193 -193
  50. {nltk-3.2 → nltk-3.3}/nltk/classify/util.py +324 -312
  51. {nltk-3.2 → nltk-3.3}/nltk/classify/weka.py +346 -345
  52. {nltk-3.2 → nltk-3.3}/nltk/cluster/__init__.py +86 -86
  53. {nltk-3.2 → nltk-3.3}/nltk/cluster/api.py +74 -70
  54. {nltk-3.2 → nltk-3.3}/nltk/cluster/em.py +247 -247
  55. {nltk-3.2 → nltk-3.3}/nltk/cluster/gaac.py +168 -168
  56. {nltk-3.2 → nltk-3.3}/nltk/cluster/kmeans.py +221 -221
  57. {nltk-3.2 → nltk-3.3}/nltk/cluster/util.py +304 -289
  58. nltk-3.3/nltk/collections.py +640 -0
  59. {nltk-3.2 → nltk-3.3}/nltk/collocations.py +388 -388
  60. nltk-3.3/nltk/compat.py +365 -0
  61. {nltk-3.2 → nltk-3.3}/nltk/corpus/__init__.py +341 -320
  62. {nltk-3.2 → nltk-3.3}/nltk/corpus/europarl_raw.py +44 -44
  63. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/__init__.py +147 -145
  64. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/aligned.py +115 -114
  65. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/api.py +448 -446
  66. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bnc.py +252 -252
  67. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bracket_parse.py +227 -227
  68. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/categorized_sents.py +179 -178
  69. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chasen.py +140 -139
  70. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/childes.py +522 -494
  71. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chunked.py +212 -212
  72. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/cmudict.py +97 -95
  73. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/comparative_sents.py +280 -278
  74. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/conll.py +528 -521
  75. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/crubadan.py +116 -116
  76. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/dependency.py +101 -101
  77. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/framenet.py +3059 -2093
  78. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ieer.py +112 -111
  79. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/indian.py +85 -86
  80. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ipipan.py +332 -331
  81. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/knbc.py +187 -187
  82. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/lin.py +156 -156
  83. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/mte.py +311 -310
  84. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nkjp.py +429 -428
  85. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nombank.py +421 -421
  86. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nps_chat.py +73 -73
  87. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/opinion_lexicon.py +115 -115
  88. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/panlex_lite.py +165 -165
  89. nltk-3.3/nltk/corpus/reader/pl196x.py +292 -0
  90. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/plaintext.py +232 -233
  91. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ppattach.py +96 -95
  92. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/propbank.py +479 -481
  93. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/pros_cons.py +128 -126
  94. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/reviews.py +330 -328
  95. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/rte.py +144 -146
  96. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/semcor.py +256 -256
  97. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/senseval.py +203 -201
  98. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sentiwordnet.py +137 -137
  99. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sinica_treebank.py +75 -75
  100. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/string_category.py +60 -59
  101. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/switchboard.py +119 -119
  102. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/tagged.py +295 -294
  103. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/timit.py +452 -450
  104. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/toolbox.py +68 -68
  105. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/twitter.py +155 -157
  106. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/udhr.py +80 -80
  107. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/util.py +804 -800
  108. nltk-3.3/nltk/corpus/reader/verbnet.py +595 -0
  109. nltk-3.3/nltk/corpus/reader/wordlist.py +134 -0
  110. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/wordnet.py +2108 -2036
  111. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/xmldocs.py +392 -387
  112. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ycoe.py +243 -242
  113. {nltk-3.2 → nltk-3.3}/nltk/corpus/util.py +144 -127
  114. {nltk-3.2 → nltk-3.3}/nltk/data.py +1487 -1451
  115. {nltk-3.2 → nltk-3.3}/nltk/decorators.py +219 -215
  116. {nltk-3.2 → nltk-3.3}/nltk/downloader.py +2278 -2274
  117. {nltk-3.2 → nltk-3.3}/nltk/draw/__init__.py +27 -27
  118. {nltk-3.2 → nltk-3.3}/nltk/draw/cfg.py +774 -777
  119. {nltk-3.2 → nltk-3.3}/nltk/draw/dispersion.py +58 -58
  120. {nltk-3.2 → nltk-3.3}/nltk/draw/table.py +1098 -1098
  121. {nltk-3.2 → nltk-3.3}/nltk/draw/tree.py +963 -966
  122. {nltk-3.2 → nltk-3.3}/nltk/draw/util.py +2356 -2356
  123. {nltk-3.2 → nltk-3.3}/nltk/featstruct.py +2617 -2500
  124. {nltk-3.2 → nltk-3.3}/nltk/grammar.py +1559 -1516
  125. {nltk-3.2 → nltk-3.3}/nltk/help.py +56 -56
  126. {nltk-3.2 → nltk-3.3}/nltk/inference/__init__.py +20 -20
  127. {nltk-3.2 → nltk-3.3}/nltk/inference/api.py +597 -589
  128. {nltk-3.2 → nltk-3.3}/nltk/inference/discourse.py +612 -608
  129. {nltk-3.2 → nltk-3.3}/nltk/inference/mace.py +311 -311
  130. {nltk-3.2 → nltk-3.3}/nltk/inference/nonmonotonic.py +509 -509
  131. {nltk-3.2 → nltk-3.3}/nltk/inference/prover9.py +431 -431
  132. {nltk-3.2 → nltk-3.3}/nltk/inference/resolution.py +687 -687
  133. {nltk-3.2 → nltk-3.3}/nltk/inference/tableau.py +607 -607
  134. {nltk-3.2 → nltk-3.3}/nltk/internals.py +982 -982
  135. {nltk-3.2 → nltk-3.3}/nltk/jsontags.py +63 -63
  136. {nltk-3.2 → nltk-3.3}/nltk/lazyimport.py +142 -142
  137. {nltk-3.2 → nltk-3.3}/nltk/metrics/__init__.py +30 -29
  138. {nltk-3.2 → nltk-3.3}/nltk/metrics/agreement.py +421 -401
  139. nltk-3.3/nltk/metrics/aline.py +607 -0
  140. {nltk-3.2 → nltk-3.3}/nltk/metrics/association.py +414 -410
  141. {nltk-3.2 → nltk-3.3}/nltk/metrics/confusionmatrix.py +206 -206
  142. {nltk-3.2 → nltk-3.3}/nltk/metrics/distance.py +207 -202
  143. {nltk-3.2 → nltk-3.3}/nltk/metrics/paice.py +381 -381
  144. {nltk-3.2 → nltk-3.3}/nltk/metrics/scores.py +228 -228
  145. {nltk-3.2 → nltk-3.3}/nltk/metrics/segmentation.py +231 -232
  146. {nltk-3.2 → nltk-3.3}/nltk/metrics/spearman.py +69 -69
  147. {nltk-3.2 → nltk-3.3}/nltk/misc/__init__.py +11 -11
  148. {nltk-3.2 → nltk-3.3}/nltk/misc/babelfish.py +10 -10
  149. {nltk-3.2 → nltk-3.3}/nltk/misc/chomsky.py +135 -133
  150. {nltk-3.2 → nltk-3.3}/nltk/misc/minimalset.py +83 -83
  151. {nltk-3.2 → nltk-3.3}/nltk/misc/sort.py +157 -157
  152. {nltk-3.2 → nltk-3.3}/nltk/misc/wordfinder.py +129 -129
  153. {nltk-3.2 → nltk-3.3}/nltk/parse/__init__.py +81 -80
  154. {nltk-3.2 → nltk-3.3}/nltk/parse/api.py +66 -66
  155. {nltk-3.2 → nltk-3.3}/nltk/parse/bllip.py +282 -282
  156. {nltk-3.2 → nltk-3.3}/nltk/parse/chart.py +1682 -1681
  157. nltk-3.3/nltk/parse/corenlp.py +780 -0
  158. {nltk-3.2 → nltk-3.3}/nltk/parse/dependencygraph.py +760 -758
  159. {nltk-3.2 → nltk-3.3}/nltk/parse/earleychart.py +452 -451
  160. {nltk-3.2 → nltk-3.3}/nltk/parse/evaluate.py +129 -129
  161. {nltk-3.2 → nltk-3.3}/nltk/parse/featurechart.py +580 -578
  162. {nltk-3.2 → nltk-3.3}/nltk/parse/generate.py +87 -76
  163. {nltk-3.2 → nltk-3.3}/nltk/parse/malt.py +356 -356
  164. {nltk-3.2 → nltk-3.3}/nltk/parse/nonprojectivedependencyparser.py +783 -783
  165. {nltk-3.2 → nltk-3.3}/nltk/parse/pchart.py +522 -522
  166. {nltk-3.2 → nltk-3.3}/nltk/parse/projectivedependencyparser.py +579 -578
  167. {nltk-3.2 → nltk-3.3}/nltk/parse/recursivedescent.py +655 -656
  168. {nltk-3.2 → nltk-3.3}/nltk/parse/shiftreduce.py +458 -459
  169. {nltk-3.2 → nltk-3.3}/nltk/parse/stanford.py +431 -406
  170. {nltk-3.2 → nltk-3.3}/nltk/parse/transitionparser.py +774 -772
  171. {nltk-3.2 → nltk-3.3}/nltk/parse/util.py +231 -231
  172. {nltk-3.2 → nltk-3.3}/nltk/parse/viterbi.py +401 -401
  173. {nltk-3.2 → nltk-3.3}/nltk/probability.py +2410 -2355
  174. {nltk-3.2 → nltk-3.3}/nltk/sem/__init__.py +61 -61
  175. {nltk-3.2 → nltk-3.3}/nltk/sem/boxer.py +1261 -1261
  176. {nltk-3.2 → nltk-3.3}/nltk/sem/chat80.py +782 -782
  177. {nltk-3.2 → nltk-3.3}/nltk/sem/cooper_storage.py +118 -118
  178. {nltk-3.2 → nltk-3.3}/nltk/sem/drt.py +1258 -1257
  179. {nltk-3.2 → nltk-3.3}/nltk/sem/drt_glue_demo.py +485 -489
  180. {nltk-3.2 → nltk-3.3}/nltk/sem/evaluate.py +790 -784
  181. {nltk-3.2 → nltk-3.3}/nltk/sem/glue.py +696 -694
  182. {nltk-3.2 → nltk-3.3}/nltk/sem/hole.py +386 -384
  183. {nltk-3.2 → nltk-3.3}/nltk/sem/lfg.py +210 -208
  184. {nltk-3.2 → nltk-3.3}/nltk/sem/linearlogic.py +451 -449
  185. {nltk-3.2 → nltk-3.3}/nltk/sem/logic.py +1910 -1910
  186. {nltk-3.2 → nltk-3.3}/nltk/sem/relextract.py +476 -483
  187. {nltk-3.2 → nltk-3.3}/nltk/sem/skolemize.py +101 -101
  188. {nltk-3.2 → nltk-3.3}/nltk/sem/util.py +249 -249
  189. {nltk-3.2 → nltk-3.3}/nltk/sentiment/__init__.py +14 -14
  190. {nltk-3.2 → nltk-3.3}/nltk/sentiment/sentiment_analyzer.py +229 -229
  191. {nltk-3.2 → nltk-3.3}/nltk/sentiment/util.py +762 -752
  192. {nltk-3.2 → nltk-3.3}/nltk/sentiment/vader.py +443 -445
  193. {nltk-3.2 → nltk-3.3}/nltk/stem/__init__.py +31 -33
  194. {nltk-3.2 → nltk-3.3}/nltk/stem/api.py +28 -25
  195. nltk-3.3/nltk/stem/arlstem.py +369 -0
  196. {nltk-3.2 → nltk-3.3}/nltk/stem/isri.py +345 -345
  197. {nltk-3.2 → nltk-3.3}/nltk/stem/lancaster.py +337 -310
  198. nltk-3.3/nltk/stem/porter.py +710 -0
  199. {nltk-3.2 → nltk-3.3}/nltk/stem/regexp.py +61 -61
  200. {nltk-3.2 → nltk-3.3}/nltk/stem/rslp.py +142 -142
  201. {nltk-3.2 → nltk-3.3}/nltk/stem/snowball.py +4236 -3712
  202. {nltk-3.2 → nltk-3.3}/nltk/stem/util.py +22 -12
  203. {nltk-3.2 → nltk-3.3}/nltk/stem/wordnet.py +51 -51
  204. {nltk-3.2 → nltk-3.3}/nltk/tag/__init__.py +152 -127
  205. {nltk-3.2 → nltk-3.3}/nltk/tag/api.py +86 -81
  206. {nltk-3.2 → nltk-3.3}/nltk/tag/brill.py +424 -426
  207. {nltk-3.2 → nltk-3.3}/nltk/tag/brill_trainer.py +608 -608
  208. {nltk-3.2 → nltk-3.3}/nltk/tag/crf.py +204 -200
  209. {nltk-3.2 → nltk-3.3}/nltk/tag/hmm.py +1277 -1277
  210. {nltk-3.2 → nltk-3.3}/nltk/tag/hunpos.py +132 -132
  211. {nltk-3.2 → nltk-3.3}/nltk/tag/mapping.py +101 -101
  212. {nltk-3.2 → nltk-3.3}/nltk/tag/perceptron.py +329 -314
  213. {nltk-3.2 → nltk-3.3}/nltk/tag/senna.py +147 -145
  214. {nltk-3.2 → nltk-3.3}/nltk/tag/sequential.py +740 -736
  215. {nltk-3.2 → nltk-3.3}/nltk/tag/stanford.py +219 -196
  216. {nltk-3.2 → nltk-3.3}/nltk/tag/tnt.py +595 -604
  217. {nltk-3.2 → nltk-3.3}/nltk/tag/util.py +72 -72
  218. {nltk-3.2 → nltk-3.3}/nltk/tbl/__init__.py +28 -28
  219. {nltk-3.2 → nltk-3.3}/nltk/tbl/api.py +1 -1
  220. {nltk-3.2 → nltk-3.3}/nltk/tbl/demo.py +366 -366
  221. {nltk-3.2 → nltk-3.3}/nltk/tbl/erroranalysis.py +39 -39
  222. {nltk-3.2 → nltk-3.3}/nltk/tbl/feature.py +259 -261
  223. {nltk-3.2 → nltk-3.3}/nltk/tbl/rule.py +321 -321
  224. {nltk-3.2 → nltk-3.3}/nltk/tbl/template.py +303 -308
  225. {nltk-3.2 → nltk-3.3}/nltk/test/__init__.py +18 -18
  226. {nltk-3.2 → nltk-3.3}/nltk/test/all.py +24 -23
  227. {nltk-3.2 → nltk-3.3}/nltk/test/bleu.doctest +14 -14
  228. {nltk-3.2 → nltk-3.3}/nltk/test/bnc.doctest +60 -60
  229. {nltk-3.2 → nltk-3.3}/nltk/test/ccg.doctest +376 -395
  230. nltk-3.3/nltk/test/ccg_semantics.doctest +553 -0
  231. {nltk-3.2 → nltk-3.3}/nltk/test/chat80.doctest +234 -234
  232. {nltk-3.2 → nltk-3.3}/nltk/test/childes.doctest +184 -184
  233. {nltk-3.2 → nltk-3.3}/nltk/test/childes_fixt.py +14 -13
  234. {nltk-3.2 → nltk-3.3}/nltk/test/chunk.doctest +373 -373
  235. {nltk-3.2 → nltk-3.3}/nltk/test/classify.doctest +185 -185
  236. {nltk-3.2 → nltk-3.3}/nltk/test/classify_fixt.py +11 -10
  237. nltk-3.3/nltk/test/collections.doctest +20 -0
  238. {nltk-3.2 → nltk-3.3}/nltk/test/collocations.doctest +276 -276
  239. {nltk-3.2 → nltk-3.3}/nltk/test/compat.doctest +134 -138
  240. {nltk-3.2 → nltk-3.3}/nltk/test/compat_fixt.py +9 -8
  241. nltk-3.3/nltk/test/concordance.doctest +55 -0
  242. {nltk-3.2 → nltk-3.3}/nltk/test/corpus.doctest +2200 -2212
  243. {nltk-3.2 → nltk-3.3}/nltk/test/corpus_fixt.py +3 -3
  244. {nltk-3.2 → nltk-3.3}/nltk/test/crubadan.doctest +65 -65
  245. {nltk-3.2 → nltk-3.3}/nltk/test/data.doctest +379 -379
  246. {nltk-3.2 → nltk-3.3}/nltk/test/dependency.doctest +241 -241
  247. {nltk-3.2 → nltk-3.3}/nltk/test/discourse.doctest +546 -546
  248. {nltk-3.2 → nltk-3.3}/nltk/test/discourse_fixt.py +14 -13
  249. {nltk-3.2 → nltk-3.3}/nltk/test/doctest_nose_plugin.py +159 -156
  250. {nltk-3.2 → nltk-3.3}/nltk/test/drt.doctest +517 -517
  251. {nltk-3.2 → nltk-3.3}/nltk/test/featgram.doctest +607 -607
  252. {nltk-3.2 → nltk-3.3}/nltk/test/featstruct.doctest +1229 -1230
  253. {nltk-3.2 → nltk-3.3}/nltk/test/framenet.doctest +288 -239
  254. {nltk-3.2 → nltk-3.3}/nltk/test/generate.doctest +67 -67
  255. {nltk-3.2 → nltk-3.3}/nltk/test/gensim.doctest +140 -141
  256. {nltk-3.2 → nltk-3.3}/nltk/test/gensim_fixt.py +10 -9
  257. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics.doctest +384 -386
  258. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt.doctest +68 -68
  259. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt_fixt.py +12 -11
  260. {nltk-3.2 → nltk-3.3}/nltk/test/grammar.doctest +48 -48
  261. {nltk-3.2 → nltk-3.3}/nltk/test/grammartestsuites.doctest +109 -109
  262. {nltk-3.2 → nltk-3.3}/nltk/test/index.doctest +100 -100
  263. {nltk-3.2 → nltk-3.3}/nltk/test/inference.doctest +534 -534
  264. {nltk-3.2 → nltk-3.3}/nltk/test/inference_fixt.py +12 -11
  265. {nltk-3.2 → nltk-3.3}/nltk/test/internals.doctest +140 -140
  266. {nltk-3.2 → nltk-3.3}/nltk/test/japanese.doctest +48 -48
  267. {nltk-3.2 → nltk-3.3}/nltk/test/logic.doctest +1098 -1098
  268. {nltk-3.2 → nltk-3.3}/nltk/test/metrics.doctest +270 -270
  269. {nltk-3.2 → nltk-3.3}/nltk/test/misc.doctest +118 -118
  270. {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic.doctest +286 -286
  271. {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic_fixt.py +12 -11
  272. {nltk-3.2 → nltk-3.3}/nltk/test/paice.doctest +35 -35
  273. {nltk-3.2 → nltk-3.3}/nltk/test/parse.doctest +884 -884
  274. {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en.doctest +565 -565
  275. {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en_fixt.py +16 -16
  276. {nltk-3.2 → nltk-3.3}/nltk/test/probability.doctest +304 -304
  277. {nltk-3.2 → nltk-3.3}/nltk/test/probability_fixt.py +13 -12
  278. {nltk-3.2 → nltk-3.3}/nltk/test/propbank.doctest +176 -176
  279. {nltk-3.2 → nltk-3.3}/nltk/test/relextract.doctest +263 -263
  280. {nltk-3.2 → nltk-3.3}/nltk/test/resolution.doctest +221 -221
  281. {nltk-3.2 → nltk-3.3}/nltk/test/runtests.py +86 -64
  282. {nltk-3.2 → nltk-3.3}/nltk/test/segmentation_fixt.py +10 -10
  283. {nltk-3.2 → nltk-3.3}/nltk/test/semantics.doctest +665 -665
  284. {nltk-3.2 → nltk-3.3}/nltk/test/semantics_fixt.py +7 -7
  285. {nltk-3.2 → nltk-3.3}/nltk/test/sentiment.doctest +233 -233
  286. {nltk-3.2 → nltk-3.3}/nltk/test/sentiwordnet.doctest +41 -41
  287. {nltk-3.2 → nltk-3.3}/nltk/test/simple.doctest +85 -85
  288. {nltk-3.2 → nltk-3.3}/nltk/test/stem.doctest +78 -78
  289. {nltk-3.2 → nltk-3.3}/nltk/test/tag.doctest +33 -33
  290. {nltk-3.2 → nltk-3.3}/nltk/test/tokenize.doctest +273 -166
  291. {nltk-3.2 → nltk-3.3}/nltk/test/toolbox.doctest +307 -307
  292. {nltk-3.2 → nltk-3.3}/nltk/test/translate.doctest +242 -242
  293. {nltk-3.2 → nltk-3.3}/nltk/test/translate_fixt.py +3 -3
  294. {nltk-3.2 → nltk-3.3}/nltk/test/tree.doctest +1101 -1101
  295. {nltk-3.2 → nltk-3.3}/nltk/test/treeprettyprinter.doctest +127 -127
  296. {nltk-3.2 → nltk-3.3}/nltk/test/treetransforms.doctest +156 -156
  297. nltk-3.3/nltk/test/unit/test_2x_compat.py +63 -0
  298. nltk-3.3/nltk/test/unit/test_aline.py +52 -0
  299. nltk-3.3/nltk/test/unit/test_chunk.py +56 -0
  300. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_classify.py +55 -53
  301. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_collocations.py +88 -79
  302. nltk-3.3/nltk/test/unit/test_concordance.py +98 -0
  303. nltk-3.3/nltk/test/unit/test_corenlp.py +1000 -0
  304. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpora.py +200 -183
  305. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpus_views.py +45 -45
  306. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_hmm.py +88 -88
  307. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_json2csv_corpus.py +188 -187
  308. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_naivebayes.py +26 -26
  309. nltk-3.3/nltk/test/unit/test_rte_classify.py +86 -0
  310. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_seekable_unicode_stream_reader.py +124 -124
  311. nltk-3.3/nltk/test/unit/test_senna.py +74 -0
  312. nltk-3.3/nltk/test/unit/test_stem.py +121 -0
  313. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tag.py +20 -20
  314. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tgrep.py +628 -626
  315. nltk-3.3/nltk/test/unit/test_tokenize.py +168 -0
  316. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_twitter_auth.py +185 -185
  317. nltk-3.3/nltk/test/unit/test_wordnet.py +134 -0
  318. nltk-3.3/nltk/test/unit/translate/test_bleu.py +244 -0
  319. nltk-3.3/nltk/test/unit/translate/test_gdfa.py +57 -0
  320. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm1.py +75 -75
  321. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm2.py +87 -87
  322. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm3.py +105 -105
  323. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm4.py +126 -126
  324. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm5.py +166 -166
  325. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm_model.py +270 -270
  326. nltk-3.3/nltk/test/unit/translate/test_nist.py +37 -0
  327. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_stack_decoder.py +300 -300
  328. {nltk-3.2 → nltk-3.3}/nltk/test/unit/utils.py +41 -41
  329. {nltk-3.2 → nltk-3.3}/nltk/test/util.doctest +48 -48
  330. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet.doctest +595 -581
  331. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_fixt.py +7 -6
  332. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_lch.doctest +53 -53
  333. {nltk-3.2 → nltk-3.3}/nltk/test/wsd.doctest +68 -68
  334. {nltk-3.2 → nltk-3.3}/nltk/text.py +663 -610
  335. {nltk-3.2 → nltk-3.3}/nltk/tgrep.py +938 -937
  336. {nltk-3.2 → nltk-3.3}/nltk/tokenize/__init__.py +130 -107
  337. {nltk-3.2 → nltk-3.3}/nltk/tokenize/api.py +77 -78
  338. {nltk-3.2 → nltk-3.3}/nltk/tokenize/casual.py +343 -342
  339. {nltk-3.2 → nltk-3.3}/nltk/tokenize/mwe.py +120 -120
  340. nltk-3.3/nltk/tokenize/nist.py +173 -0
  341. {nltk-3.2 → nltk-3.3}/nltk/tokenize/punkt.py +1620 -1601
  342. {nltk-3.2 → nltk-3.3}/nltk/tokenize/regexp.py +208 -202
  343. nltk-3.3/nltk/tokenize/repp.py +151 -0
  344. {nltk-3.2 → nltk-3.3}/nltk/tokenize/sexpr.py +140 -142
  345. {nltk-3.2 → nltk-3.3}/nltk/tokenize/simple.py +138 -136
  346. {nltk-3.2 → nltk-3.3}/nltk/tokenize/stanford.py +117 -113
  347. nltk-3.3/nltk/tokenize/stanford_segmenter.py +246 -0
  348. {nltk-3.2 → nltk-3.3}/nltk/tokenize/texttiling.py +458 -460
  349. nltk-3.3/nltk/tokenize/toktok.py +155 -0
  350. nltk-3.3/nltk/tokenize/treebank.py +363 -0
  351. nltk-3.3/nltk/tokenize/util.py +261 -0
  352. {nltk-3.2 → nltk-3.3}/nltk/toolbox.py +499 -497
  353. {nltk-3.2 → nltk-3.3}/nltk/translate/__init__.py +24 -24
  354. {nltk-3.2 → nltk-3.3}/nltk/translate/api.py +331 -321
  355. {nltk-3.2 → nltk-3.3}/nltk/translate/bleu_score.py +615 -556
  356. nltk-3.3/nltk/translate/chrf_score.py +137 -0
  357. {nltk-3.2 → nltk-3.3}/nltk/translate/gale_church.py +248 -235
  358. {nltk-3.2 → nltk-3.3}/nltk/translate/gdfa.py +135 -131
  359. nltk-3.3/nltk/translate/gleu_score.py +193 -0
  360. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm1.py +250 -241
  361. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm2.py +308 -308
  362. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm3.py +337 -337
  363. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm4.py +467 -467
  364. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm5.py +639 -639
  365. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm_model.py +536 -533
  366. {nltk-3.2 → nltk-3.3}/nltk/translate/metrics.py +40 -40
  367. nltk-3.3/nltk/translate/nist_score.py +178 -0
  368. {nltk-3.2 → nltk-3.3}/nltk/translate/phrase_based.py +175 -175
  369. {nltk-3.2 → nltk-3.3}/nltk/translate/ribes_score.py +325 -325
  370. {nltk-3.2 → nltk-3.3}/nltk/translate/stack_decoder.py +499 -495
  371. {nltk-3.2 → nltk-3.3}/nltk/tree.py +1605 -1603
  372. {nltk-3.2 → nltk-3.3}/nltk/treeprettyprinter.py +564 -564
  373. {nltk-3.2 → nltk-3.3}/nltk/treetransforms.py +309 -309
  374. {nltk-3.2 → nltk-3.3}/nltk/twitter/__init__.py +28 -28
  375. {nltk-3.2 → nltk-3.3}/nltk/twitter/api.py +141 -136
  376. {nltk-3.2 → nltk-3.3}/nltk/twitter/common.py +264 -258
  377. {nltk-3.2 → nltk-3.3}/nltk/twitter/twitter_demo.py +288 -288
  378. {nltk-3.2 → nltk-3.3}/nltk/twitter/twitterclient.py +530 -530
  379. {nltk-3.2 → nltk-3.3}/nltk/twitter/util.py +147 -146
  380. nltk-3.3/nltk/util.py +751 -0
  381. {nltk-3.2 → nltk-3.3}/nltk/wsd.py +53 -53
  382. {nltk-3.2 → nltk-3.3/nltk.egg-info}/PKG-INFO +13 -5
  383. {nltk-3.2 → nltk-3.3}/nltk.egg-info/SOURCES.txt +26 -3
  384. nltk-3.3/nltk.egg-info/requires.txt +31 -0
  385. {nltk-3.2 → nltk-3.3}/setup.cfg +0 -1
  386. {nltk-3.2 → nltk-3.3}/setup.py +102 -75
  387. nltk-3.2/nltk/VERSION +0 -1
  388. nltk-3.2/nltk/compat.py +0 -680
  389. nltk-3.2/nltk/corpus/reader/pl196x.py +0 -278
  390. nltk-3.2/nltk/corpus/reader/verbnet.py +0 -408
  391. nltk-3.2/nltk/corpus/reader/wordlist.py +0 -37
  392. nltk-3.2/nltk/sentiment/vader_lexicon.txt +0 -7517
  393. nltk-3.2/nltk/six.py +0 -868
  394. nltk-3.2/nltk/stem/porter.py +0 -692
  395. nltk-3.2/nltk/test/unit/test_2x_compat.py +0 -26
  396. nltk-3.2/nltk/test/unit/test_stem.py +0 -36
  397. nltk-3.2/nltk/test/unit/test_tokenize.py +0 -23
  398. nltk-3.2/nltk/test/unit/translate/test_bleu.py +0 -84
  399. nltk-3.2/nltk/tokenize/treebank.py +0 -124
  400. nltk-3.2/nltk/tokenize/util.py +0 -90
  401. nltk-3.2/nltk/util.py +0 -1364
  402. {nltk-3.2 → nltk-3.3}/nltk/test/unit/__init__.py +0 -0
  403. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/__init__.py +0 -0
  404. {nltk-3.2 → nltk-3.3}/nltk.egg-info/dependency_links.txt +0 -0
  405. {nltk-3.2 → nltk-3.3}/nltk.egg-info/not-zip-safe +0 -0
  406. {nltk-3.2 → nltk-3.3}/nltk.egg-info/top_level.txt +0 -0
@@ -1,752 +1,762 @@
1
- # coding: utf-8
2
- #
3
- # Natural Language Toolkit: Sentiment Analyzer
4
- #
5
- # Copyright (C) 2001-2015 NLTK Project
6
- # Author: Pierpaolo Pantone <24alsecondo@gmail.com>
7
- # URL: <http://nltk.org/>
8
- # For license information, see LICENSE.TXT
9
-
10
- """
11
- Utility methods for Sentiment Analysis.
12
- """
13
-
14
- from copy import deepcopy
15
- import codecs
16
- import csv
17
- import json
18
- import pickle
19
- import random
20
- import re
21
- import sys
22
- import time
23
-
24
- import nltk
25
- from nltk.corpus import CategorizedPlaintextCorpusReader
26
- from nltk.data import load
27
- from nltk.tokenize.casual import EMOTICON_RE
28
- from nltk.twitter.common import outf_writer_compat, extract_fields
29
-
30
- #////////////////////////////////////////////////////////////
31
- #{ Regular expressions
32
- #////////////////////////////////////////////////////////////
33
-
34
- # Regular expression for negation by Christopher Potts
35
- NEGATION = r"""
36
- (?:
37
- ^(?:never|no|nothing|nowhere|noone|none|not|
38
- havent|hasnt|hadnt|cant|couldnt|shouldnt|
39
- wont|wouldnt|dont|doesnt|didnt|isnt|arent|aint
40
- )$
41
- )
42
- |
43
- n't"""
44
-
45
- NEGATION_RE = re.compile(NEGATION, re.VERBOSE)
46
-
47
- CLAUSE_PUNCT = r'^[.:;!?]$'
48
- CLAUSE_PUNCT_RE = re.compile(CLAUSE_PUNCT)
49
-
50
- # Happy and sad emoticons
51
-
52
- HAPPY = set([
53
- ':-)', ':)', ';)', ':o)', ':]', ':3', ':c)', ':>', '=]', '8)', '=)', ':}',
54
- ':^)', ':-D', ':D', '8-D', '8D', 'x-D', 'xD', 'X-D', 'XD', '=-D', '=D',
55
- '=-3', '=3', ':-))', ":'-)", ":')", ':*', ':^*', '>:P', ':-P', ':P', 'X-P',
56
- 'x-p', 'xp', 'XP', ':-p', ':p', '=p', ':-b', ':b', '>:)', '>;)', '>:-)',
57
- '<3'
58
- ])
59
-
60
- SAD = set([
61
- ':L', ':-/', '>:/', ':S', '>:[', ':@', ':-(', ':[', ':-||', '=L', ':<',
62
- ':-[', ':-<', '=\\', '=/', '>:(', ':(', '>.<', ":'-(", ":'(", ':\\', ':-c',
63
- ':c', ':{', '>:\\', ';('
64
- ])
65
-
66
- def timer(method):
67
- """
68
- A timer decorator to measure execution performance of methods.
69
- """
70
- def timed(*args, **kw):
71
- start = time.time()
72
- result = method(*args, **kw)
73
- end = time.time()
74
- tot_time = end - start
75
- hours = int(tot_time / 3600)
76
- mins = int((tot_time / 60) % 60)
77
- # in Python 2.x round() will return a float, so we convert it to int
78
- secs = int(round(tot_time % 60))
79
- if hours == 0 and mins == 0 and secs < 10:
80
- print('[TIMER] {0}(): {:.3f} seconds'.format(method.__name__, tot_time))
81
- else:
82
- print('[TIMER] {0}(): {1}h {2}m {3}s'.format(method.__name__, hours, mins, secs))
83
- return result
84
- return timed
85
-
86
- #////////////////////////////////////////////////////////////
87
- #{ Feature extractor functions
88
- #////////////////////////////////////////////////////////////
89
- """
90
- Feature extractor functions are declared outside the SentimentAnalyzer class.
91
- Users should have the possibility to create their own feature extractors
92
- without modifying SentimentAnalyzer.
93
- """
94
-
95
- def extract_unigram_feats(document, unigrams, handle_negation=False):
96
- """
97
- Populate a dictionary of unigram features, reflecting the presence/absence in
98
- the document of each of the tokens in `unigrams`.
99
-
100
- :param document: a list of words/tokens.
101
- :param unigrams: a list of words/tokens whose presence/absence has to be
102
- checked in `document`.
103
- :param handle_negation: if `handle_negation == True` apply `mark_negation`
104
- method to `document` before checking for unigram presence/absence.
105
- :return: a dictionary of unigram features {unigram : boolean}.
106
-
107
- >>> words = ['ice', 'police', 'riot']
108
- >>> document = 'ice is melting due to global warming'.split()
109
- >>> sorted(extract_unigram_feats(document, words).items())
110
- [('contains(ice)', True), ('contains(police)', False), ('contains(riot)', False)]
111
- """
112
- features = {}
113
- if handle_negation:
114
- document = mark_negation(document)
115
- for word in unigrams:
116
- features['contains({0})'.format(word)] = word in set(document)
117
- return features
118
-
119
- def extract_bigram_feats(document, bigrams):
120
- """
121
- Populate a dictionary of bigram features, reflecting the presence/absence in
122
- the document of each of the tokens in `bigrams`. This extractor function only
123
- considers contiguous bigrams obtained by `nltk.bigrams`.
124
-
125
- :param document: a list of words/tokens.
126
- :param unigrams: a list of bigrams whose presence/absence has to be
127
- checked in `document`.
128
- :return: a dictionary of bigram features {bigram : boolean}.
129
-
130
- >>> bigrams = [('global', 'warming'), ('police', 'prevented'), ('love', 'you')]
131
- >>> document = 'ice is melting due to global warming'.split()
132
- >>> sorted(extract_bigram_feats(document, bigrams).items())
133
- [('contains(global - warming)', True), ('contains(love - you)', False),
134
- ('contains(police - prevented)', False)]
135
- """
136
- features = {}
137
- for bigr in bigrams:
138
- features['contains({0} - {1})'.format(bigr[0], bigr[1])] = bigr in nltk.bigrams(document)
139
- return features
140
-
141
- #////////////////////////////////////////////////////////////
142
- #{ Helper Functions
143
- #////////////////////////////////////////////////////////////
144
-
145
- def mark_negation(document, double_neg_flip=False, shallow=False):
146
- """
147
- Append _NEG suffix to words that appear in the scope between a negation
148
- and a punctuation mark.
149
-
150
- :param document: a list of words/tokens, or a tuple (words, label).
151
- :param shallow: if True, the method will modify the original document in place.
152
- :param double_neg_flip: if True, double negation is considered affirmation
153
- (we activate/deactivate negation scope everytime we find a negation).
154
- :return: if `shallow == True` the method will modify the original document
155
- and return it. If `shallow == False` the method will return a modified
156
- document, leaving the original unmodified.
157
-
158
- >>> sent = "I didn't like this movie . It was bad .".split()
159
- >>> mark_negation(sent)
160
- ['I', "didn't", 'like_NEG', 'this_NEG', 'movie_NEG', '.', 'It', 'was', 'bad', '.']
161
- """
162
- if not shallow:
163
- document = deepcopy(document)
164
- # check if the document is labeled. If so, do not consider the label.
165
- labeled = document and isinstance(document[0], (tuple, list))
166
- if labeled:
167
- doc = document[0]
168
- else:
169
- doc = document
170
- neg_scope = False
171
- for i, word in enumerate(doc):
172
- if NEGATION_RE.search(word):
173
- if not neg_scope or (neg_scope and double_neg_flip):
174
- neg_scope = not neg_scope
175
- continue
176
- else:
177
- doc[i] += '_NEG'
178
- elif neg_scope and CLAUSE_PUNCT_RE.search(word):
179
- neg_scope = not neg_scope
180
- elif neg_scope and not CLAUSE_PUNCT_RE.search(word):
181
- doc[i] += '_NEG'
182
-
183
- return document
184
-
185
- def output_markdown(filename, **kwargs):
186
- """
187
- Write the output of an analysis to a file.
188
- """
189
- with codecs.open(filename, 'at') as outfile:
190
- text = '\n*** \n\n'
191
- text += '{0} \n\n'.format(time.strftime("%d/%m/%Y, %H:%M"))
192
- for k in sorted(kwargs):
193
- if isinstance(kwargs[k], dict):
194
- dictionary = kwargs[k]
195
- text += ' - **{0}:**\n'.format(k)
196
- for entry in sorted(dictionary):
197
- text += ' - {0}: {1} \n'.format(entry, dictionary[entry])
198
- elif isinstance(kwargs[k], list):
199
- text += ' - **{0}:**\n'.format(k)
200
- for entry in kwargs[k]:
201
- text += ' - {0}\n'.format(entry)
202
- else:
203
- text += ' - **{0}:** {1} \n'.format(k, kwargs[k])
204
- outfile.write(text)
205
-
206
- def save_file(content, filename):
207
- """
208
- Store `content` in `filename`. Can be used to store a SentimentAnalyzer.
209
- """
210
- print("Saving", filename)
211
- with codecs.open(filename, 'wb') as storage_file:
212
- # The protocol=2 parameter is for python2 compatibility
213
- pickle.dump(content, storage_file, protocol=2)
214
-
215
- def split_train_test(all_instances, n=None):
216
- """
217
- Randomly split `n` instances of the dataset into train and test sets.
218
-
219
- :param all_instances: a list of instances (e.g. documents) that will be split.
220
- :param n: the number of instances to consider (in case we want to use only a
221
- subset).
222
- :return: two lists of instances. Train set is 8/10 of the total and test set
223
- is 2/10 of the total.
224
- """
225
- random.seed(12345)
226
- random.shuffle(all_instances)
227
- if not n or n > len(all_instances):
228
- n = len(all_instances)
229
- train_set = all_instances[:int(.8*n)]
230
- test_set = all_instances[int(.8*n):n]
231
-
232
- return train_set, test_set
233
-
234
- def _show_plot(x_values, y_values, x_labels=None, y_labels=None):
235
- try:
236
- import matplotlib.pyplot as plt
237
- except ImportError:
238
- raise ImportError('The plot function requires matplotlib to be installed.'
239
- 'See http://matplotlib.org/')
240
-
241
- plt.locator_params(axis='y', nbins=3)
242
- axes = plt.axes()
243
- axes.yaxis.grid()
244
- plt.plot(x_values, y_values, 'ro', color='red')
245
- plt.ylim(ymin=-1.2, ymax=1.2)
246
- plt.tight_layout(pad=5)
247
- if x_labels:
248
- plt.xticks(x_values, x_labels, rotation='vertical')
249
- if y_labels:
250
- plt.yticks([-1, 0, 1], y_labels, rotation='horizontal')
251
- # Pad margins so that markers are not clipped by the axes
252
- plt.margins(0.2)
253
- plt.show()
254
-
255
- #////////////////////////////////////////////////////////////
256
- #{ Parsing and conversion functions
257
- #////////////////////////////////////////////////////////////
258
-
259
- def json2csv_preprocess(json_file, outfile, fields, encoding='utf8', errors='replace',
260
- gzip_compress=False, skip_retweets=True, skip_tongue_tweets=True,
261
- skip_ambiguous_tweets=True, strip_off_emoticons=True, remove_duplicates=True,
262
- limit=None):
263
- """
264
- Convert json file to csv file, preprocessing each row to obtain a suitable
265
- dataset for tweets Semantic Analysis.
266
-
267
- :param json_file: the original json file containing tweets.
268
- :param outfile: the output csv filename.
269
- :param fields: a list of fields that will be extracted from the json file and
270
- kept in the output csv file.
271
- :param encoding: the encoding of the files.
272
- :param errors: the error handling strategy for the output writer.
273
- :param gzip_compress: if True, create a compressed GZIP file.
274
-
275
- :param skip_retweets: if True, remove retweets.
276
- :param skip_tongue_tweets: if True, remove tweets containing ":P" and ":-P"
277
- emoticons.
278
- :param skip_ambiguous_tweets: if True, remove tweets containing both happy
279
- and sad emoticons.
280
- :param strip_off_emoticons: if True, strip off emoticons from all tweets.
281
- :param remove_duplicates: if True, remove tweets appearing more than once.
282
- :param limit: an integer to set the number of tweets to convert. After the
283
- limit is reached the conversion will stop. It can be useful to create
284
- subsets of the original tweets json data.
285
- """
286
- with codecs.open(json_file, encoding=encoding) as fp:
287
- (writer, outf) = outf_writer_compat(outfile, encoding, errors, gzip_compress)
288
- # write the list of fields as header
289
- writer.writerow(fields)
290
-
291
- if remove_duplicates == True:
292
- tweets_cache = []
293
- i = 0
294
- for line in fp:
295
- tweet = json.loads(line)
296
- row = extract_fields(tweet, fields)
297
- try:
298
- text = row[fields.index('text')]
299
- # Remove retweets
300
- if skip_retweets == True:
301
- if re.search(r'\bRT\b', text):
302
- continue
303
- # Remove tweets containing ":P" and ":-P" emoticons
304
- if skip_tongue_tweets == True:
305
- if re.search(r'\:\-?P\b', text):
306
- continue
307
- # Remove tweets containing both happy and sad emoticons
308
- if skip_ambiguous_tweets == True:
309
- all_emoticons = EMOTICON_RE.findall(text)
310
- if all_emoticons:
311
- if (set(all_emoticons) & HAPPY) and (set(all_emoticons) & SAD):
312
- continue
313
- # Strip off emoticons from all tweets
314
- if strip_off_emoticons == True:
315
- row[fields.index('text')] = re.sub(r'(?!\n)\s+', ' ', EMOTICON_RE.sub('', text))
316
- # Remove duplicate tweets
317
- if remove_duplicates == True:
318
- if row[fields.index('text')] in tweets_cache:
319
- continue
320
- else:
321
- tweets_cache.append(row[fields.index('text')])
322
- except ValueError:
323
- pass
324
- writer.writerow(row)
325
- i += 1
326
- if limit and i >= limit:
327
- break
328
- outf.close()
329
-
330
- def parse_tweets_set(filename, label, word_tokenizer=None, sent_tokenizer=None,
331
- skip_header=True):
332
- """
333
- Parse csv file containing tweets and output data a list of (text, label) tuples.
334
-
335
- :param filename: the input csv filename.
336
- :param label: the label to be appended to each tweet contained in the csv file.
337
- :param word_tokenizer: the tokenizer instance that will be used to tokenize
338
- each sentence into tokens (e.g. WordPunctTokenizer() or BlanklineTokenizer()).
339
- If no word_tokenizer is specified, tweets will not be tokenized.
340
- :param sent_tokenizer: the tokenizer that will be used to split each tweet into
341
- sentences.
342
- :param skip_header: if True, skip the first line of the csv file (which usually
343
- contains headers).
344
-
345
- :return: a list of (text, label) tuples.
346
- """
347
- tweets = []
348
- if not sent_tokenizer:
349
- sent_tokenizer = load('tokenizers/punkt/english.pickle')
350
-
351
- # If we use Python3.x we can proceed using the 'rt' flag
352
- if sys.version_info[0] == 3:
353
- with codecs.open(filename, 'rt') as csvfile:
354
- reader = csv.reader(csvfile)
355
- if skip_header == True:
356
- next(reader, None) # skip the header
357
- i = 0
358
- for tweet_id, text in reader:
359
- # text = text[1]
360
- i += 1
361
- sys.stdout.write('Loaded {0} tweets\r'.format(i))
362
- # Apply sentence and word tokenizer to text
363
- if word_tokenizer:
364
- tweet = [w for sent in sent_tokenizer.tokenize(text)
365
- for w in word_tokenizer.tokenize(sent)]
366
- else:
367
- tweet = text
368
- tweets.append((tweet, label))
369
- # If we use Python2.x we need to handle encoding problems
370
- elif sys.version_info[0] < 3:
371
- with codecs.open(filename) as csvfile:
372
- reader = csv.reader(csvfile)
373
- if skip_header == True:
374
- next(reader, None) # skip the header
375
- i = 0
376
- for row in reader:
377
- unicode_row = [x.decode('utf8') for x in row]
378
- text = unicode_row[1]
379
- i += 1
380
- sys.stdout.write('Loaded {0} tweets\r'.format(i))
381
- # Apply sentence and word tokenizer to text
382
- if word_tokenizer:
383
- tweet = [w.encode('utf8') for sent in sent_tokenizer.tokenize(text)
384
- for w in word_tokenizer.tokenize(sent)]
385
- else:
386
- tweet = text
387
- tweets.append((tweet, label))
388
- print("Loaded {0} tweets".format(i))
389
- return tweets
390
-
391
- #////////////////////////////////////////////////////////////
392
- #{ Demos
393
- #////////////////////////////////////////////////////////////
394
-
395
- def demo_tweets(trainer, n_instances=None, output=None):
396
- """
397
- Train and test Naive Bayes classifier on 10000 tweets, tokenized using
398
- TweetTokenizer.
399
- Features are composed of:
400
- - 1000 most frequent unigrams
401
- - 100 top bigrams (using BigramAssocMeasures.pmi)
402
-
403
- :param trainer: `train` method of a classifier.
404
- :param n_instances: the number of total tweets that have to be used for
405
- training and testing. Tweets will be equally split between positive and
406
- negative.
407
- :param output: the output file where results have to be reported.
408
- """
409
- from nltk.tokenize import TweetTokenizer
410
- from nltk.sentiment import SentimentAnalyzer
411
- from nltk.corpus import twitter_samples, stopwords
412
-
413
- # Different customizations for the TweetTokenizer
414
- tokenizer = TweetTokenizer(preserve_case=False)
415
- # tokenizer = TweetTokenizer(preserve_case=True, strip_handles=True)
416
- # tokenizer = TweetTokenizer(reduce_len=True, strip_handles=True)
417
-
418
- if n_instances is not None:
419
- n_instances = int(n_instances/2)
420
-
421
- fields = ['id', 'text']
422
- positive_json = twitter_samples.abspath("positive_tweets.json")
423
- positive_csv = 'positive_tweets.csv'
424
- json2csv_preprocess(positive_json, positive_csv, fields, limit=n_instances)
425
-
426
- negative_json = twitter_samples.abspath("negative_tweets.json")
427
- negative_csv = 'negative_tweets.csv'
428
- json2csv_preprocess(negative_json, negative_csv, fields, limit=n_instances)
429
-
430
- neg_docs = parse_tweets_set(negative_csv, label='neg', word_tokenizer=tokenizer)
431
- pos_docs = parse_tweets_set(positive_csv, label='pos', word_tokenizer=tokenizer)
432
-
433
- # We separately split subjective and objective instances to keep a balanced
434
- # uniform class distribution in both train and test sets.
435
- train_pos_docs, test_pos_docs = split_train_test(pos_docs)
436
- train_neg_docs, test_neg_docs = split_train_test(neg_docs)
437
-
438
- training_tweets = train_pos_docs+train_neg_docs
439
- testing_tweets = test_pos_docs+test_neg_docs
440
-
441
- sentim_analyzer = SentimentAnalyzer()
442
- # stopwords = stopwords.words('english')
443
- # all_words = [word for word in sentim_analyzer.all_words(training_tweets) if word.lower() not in stopwords]
444
- all_words = [word for word in sentim_analyzer.all_words(training_tweets)]
445
-
446
- # Add simple unigram word features
447
- unigram_feats = sentim_analyzer.unigram_word_feats(all_words, top_n=1000)
448
- sentim_analyzer.add_feat_extractor(extract_unigram_feats, unigrams=unigram_feats)
449
-
450
- # Add bigram collocation features
451
- bigram_collocs_feats = sentim_analyzer.bigram_collocation_feats([tweet[0] for tweet in training_tweets],
452
- top_n=100, min_freq=12)
453
- sentim_analyzer.add_feat_extractor(extract_bigram_feats, bigrams=bigram_collocs_feats)
454
-
455
- training_set = sentim_analyzer.apply_features(training_tweets)
456
- test_set = sentim_analyzer.apply_features(testing_tweets)
457
-
458
- classifier = sentim_analyzer.train(trainer, training_set)
459
- # classifier = sentim_analyzer.train(trainer, training_set, max_iter=4)
460
- try:
461
- classifier.show_most_informative_features()
462
- except AttributeError:
463
- print('Your classifier does not provide a show_most_informative_features() method.')
464
- results = sentim_analyzer.evaluate(test_set)
465
-
466
- if output:
467
- extr = [f.__name__ for f in sentim_analyzer.feat_extractors]
468
- output_markdown(output, Dataset='labeled_tweets', Classifier=type(classifier).__name__,
469
- Tokenizer=tokenizer.__class__.__name__, Feats=extr,
470
- Results=results, Instances=n_instances)
471
-
472
- def demo_movie_reviews(trainer, n_instances=None, output=None):
473
- """
474
- Train classifier on all instances of the Movie Reviews dataset.
475
- The corpus has been preprocessed using the default sentence tokenizer and
476
- WordPunctTokenizer.
477
- Features are composed of:
478
- - most frequent unigrams
479
-
480
- :param trainer: `train` method of a classifier.
481
- :param n_instances: the number of total reviews that have to be used for
482
- training and testing. Reviews will be equally split between positive and
483
- negative.
484
- :param output: the output file where results have to be reported.
485
- """
486
- from nltk.corpus import movie_reviews
487
- from nltk.sentiment import SentimentAnalyzer
488
-
489
- if n_instances is not None:
490
- n_instances = int(n_instances/2)
491
-
492
- pos_docs = [(list(movie_reviews.words(pos_id)), 'pos') for pos_id in movie_reviews.fileids('pos')[:n_instances]]
493
- neg_docs = [(list(movie_reviews.words(neg_id)), 'neg') for neg_id in movie_reviews.fileids('neg')[:n_instances]]
494
- # We separately split positive and negative instances to keep a balanced
495
- # uniform class distribution in both train and test sets.
496
- train_pos_docs, test_pos_docs = split_train_test(pos_docs)
497
- train_neg_docs, test_neg_docs = split_train_test(neg_docs)
498
-
499
- training_docs = train_pos_docs+train_neg_docs
500
- testing_docs = test_pos_docs+test_neg_docs
501
-
502
- sentim_analyzer = SentimentAnalyzer()
503
- all_words = sentim_analyzer.all_words(training_docs)
504
-
505
- # Add simple unigram word features
506
- unigram_feats = sentim_analyzer.unigram_word_feats(all_words, min_freq=4)
507
- sentim_analyzer.add_feat_extractor(extract_unigram_feats, unigrams=unigram_feats)
508
- # Apply features to obtain a feature-value representation of our datasets
509
- training_set = sentim_analyzer.apply_features(training_docs)
510
- test_set = sentim_analyzer.apply_features(testing_docs)
511
-
512
- classifier = sentim_analyzer.train(trainer, training_set)
513
- try:
514
- classifier.show_most_informative_features()
515
- except AttributeError:
516
- print('Your classifier does not provide a show_most_informative_features() method.')
517
- results = sentim_analyzer.evaluate(test_set)
518
-
519
- if output:
520
- extr = [f.__name__ for f in sentim_analyzer.feat_extractors]
521
- output_markdown(output, Dataset='Movie_reviews', Classifier=type(classifier).__name__,
522
- Tokenizer='WordPunctTokenizer', Feats=extr, Results=results,
523
- Instances=n_instances)
524
-
525
- def demo_subjectivity(trainer, save_analyzer=False, n_instances=None, output=None):
526
- """
527
- Train and test a classifier on instances of the Subjective Dataset by Pang and
528
- Lee. The dataset is made of 5000 subjective and 5000 objective sentences.
529
- All tokens (words and punctuation marks) are separated by a whitespace, so
530
- we use the basic WhitespaceTokenizer to parse the data.
531
-
532
- :param trainer: `train` method of a classifier.
533
- :param save_analyzer: if `True`, store the SentimentAnalyzer in a pickle file.
534
- :param n_instances: the number of total sentences that have to be used for
535
- training and testing. Sentences will be equally split between positive
536
- and negative.
537
- :param output: the output file where results have to be reported.
538
- """
539
- from nltk.sentiment import SentimentAnalyzer
540
- from nltk.corpus import subjectivity
541
-
542
- if n_instances is not None:
543
- n_instances = int(n_instances/2)
544
-
545
- subj_docs = [(sent, 'subj') for sent in subjectivity.sents(categories='subj')[:n_instances]]
546
- obj_docs = [(sent, 'obj') for sent in subjectivity.sents(categories='obj')[:n_instances]]
547
-
548
- # We separately split subjective and objective instances to keep a balanced
549
- # uniform class distribution in both train and test sets.
550
- train_subj_docs, test_subj_docs = split_train_test(subj_docs)
551
- train_obj_docs, test_obj_docs = split_train_test(obj_docs)
552
-
553
- training_docs = train_subj_docs+train_obj_docs
554
- testing_docs = test_subj_docs+test_obj_docs
555
-
556
- sentim_analyzer = SentimentAnalyzer()
557
- all_words_neg = sentim_analyzer.all_words([mark_negation(doc) for doc in training_docs])
558
-
559
- # Add simple unigram word features handling negation
560
- unigram_feats = sentim_analyzer.unigram_word_feats(all_words_neg, min_freq=4)
561
- sentim_analyzer.add_feat_extractor(extract_unigram_feats, unigrams=unigram_feats)
562
-
563
- # Apply features to obtain a feature-value representation of our datasets
564
- training_set = sentim_analyzer.apply_features(training_docs)
565
- test_set = sentim_analyzer.apply_features(testing_docs)
566
-
567
- classifier = sentim_analyzer.train(trainer, training_set)
568
- try:
569
- classifier.show_most_informative_features()
570
- except AttributeError:
571
- print('Your classifier does not provide a show_most_informative_features() method.')
572
- results = sentim_analyzer.evaluate(test_set)
573
-
574
- if save_analyzer == True:
575
- save_file(sentim_analyzer, 'sa_subjectivity.pickle')
576
-
577
- if output:
578
- extr = [f.__name__ for f in sentim_analyzer.feat_extractors]
579
- output_markdown(output, Dataset='subjectivity', Classifier=type(classifier).__name__,
580
- Tokenizer='WhitespaceTokenizer', Feats=extr,
581
- Instances=n_instances, Results=results)
582
-
583
- return sentim_analyzer
584
-
585
- def demo_sent_subjectivity(text):
586
- """
587
- Classify a single sentence as subjective or objective using a stored
588
- SentimentAnalyzer.
589
-
590
- :param text: a sentence whose subjectivity has to be classified.
591
- """
592
- from nltk.classify import NaiveBayesClassifier
593
- from nltk.tokenize import regexp
594
- word_tokenizer = regexp.WhitespaceTokenizer()
595
- try:
596
- sentim_analyzer = load('sa_subjectivity.pickle')
597
- except LookupError:
598
- print('Cannot find the sentiment analyzer you want to load.')
599
- print('Training a new one using NaiveBayesClassifier.')
600
- sentim_analyzer = demo_subjectivity(NaiveBayesClassifier.train, True)
601
-
602
- # Tokenize and convert to lower case
603
- tokenized_text = [word.lower() for word in word_tokenizer.tokenize(text)]
604
- print(sentim_analyzer.classify(tokenized_text))
605
-
606
- def demo_liu_hu_lexicon(sentence, plot=False):
607
- """
608
- Basic example of sentiment classification using Liu and Hu opinion lexicon.
609
- This function simply counts the number of positive, negative and neutral words
610
- in the sentence and classifies it depending on which polarity is more represented.
611
- Words that do not appear in the lexicon are considered as neutral.
612
-
613
- :param sentence: a sentence whose polarity has to be classified.
614
- :param plot: if True, plot a visual representation of the sentence polarity.
615
- """
616
- from nltk.corpus import opinion_lexicon
617
- from nltk.tokenize import treebank
618
-
619
- tokenizer = treebank.TreebankWordTokenizer()
620
- pos_words = 0
621
- neg_words = 0
622
- tokenized_sent = [word.lower() for word in tokenizer.tokenize(sentence)]
623
-
624
- x = list(range(len(tokenized_sent))) # x axis for the plot
625
- y = []
626
-
627
- for word in tokenized_sent:
628
- if word in opinion_lexicon.positive():
629
- pos_words += 1
630
- y.append(1) # positive
631
- elif word in opinion_lexicon.negative():
632
- neg_words += 1
633
- y.append(-1) # negative
634
- else:
635
- y.append(0) # neutral
636
-
637
- if pos_words > neg_words:
638
- print('Positive')
639
- elif pos_words < neg_words:
640
- print('Negative')
641
- elif pos_words == neg_words:
642
- print('Neutral')
643
-
644
- if plot == True:
645
- _show_plot(x, y, x_labels=tokenized_sent, y_labels=['Negative', 'Neutral', 'Positive'])
646
-
647
- def demo_vader_instance(text):
648
- """
649
- Output polarity scores for a text using Vader approach.
650
-
651
- :param text: a text whose polarity has to be evaluated.
652
- """
653
- from nltk.sentiment import SentimentIntensityAnalyzer
654
- vader_analyzer = SentimentIntensityAnalyzer()
655
- print(vader_analyzer.polarity_scores(text))
656
-
657
- def demo_vader_tweets(n_instances=None, output=None):
658
- """
659
- Classify 10000 positive and negative tweets using Vader approach.
660
-
661
- :param n_instances: the number of total tweets that have to be classified.
662
- :param output: the output file where results have to be reported.
663
- """
664
- from collections import defaultdict
665
- from nltk.corpus import twitter_samples
666
- from nltk.sentiment import SentimentIntensityAnalyzer
667
- from nltk.metrics import (accuracy as eval_accuracy, precision as eval_precision,
668
- recall as eval_recall, f_measure as eval_f_measure)
669
-
670
- if n_instances is not None:
671
- n_instances = int(n_instances/2)
672
-
673
- fields = ['id', 'text']
674
- positive_json = twitter_samples.abspath("positive_tweets.json")
675
- positive_csv = 'positive_tweets.csv'
676
- json2csv_preprocess(positive_json, positive_csv, fields, strip_off_emoticons=False,
677
- limit=n_instances)
678
-
679
- negative_json = twitter_samples.abspath("negative_tweets.json")
680
- negative_csv = 'negative_tweets.csv'
681
- json2csv_preprocess(negative_json, negative_csv, fields, strip_off_emoticons=False,
682
- limit=n_instances)
683
-
684
- pos_docs = parse_tweets_set(positive_csv, label='pos')
685
- neg_docs = parse_tweets_set(negative_csv, label='neg')
686
-
687
- # We separately split subjective and objective instances to keep a balanced
688
- # uniform class distribution in both train and test sets.
689
- train_pos_docs, test_pos_docs = split_train_test(pos_docs)
690
- train_neg_docs, test_neg_docs = split_train_test(neg_docs)
691
-
692
- training_tweets = train_pos_docs+train_neg_docs
693
- testing_tweets = test_pos_docs+test_neg_docs
694
-
695
- vader_analyzer = SentimentIntensityAnalyzer()
696
-
697
- gold_results = defaultdict(set)
698
- test_results = defaultdict(set)
699
- acc_gold_results = []
700
- acc_test_results = []
701
- labels = set()
702
- num = 0
703
- for i, (text, label) in enumerate(testing_tweets):
704
- labels.add(label)
705
- gold_results[label].add(i)
706
- acc_gold_results.append(label)
707
- score = vader_analyzer.polarity_scores(text)['compound']
708
- if score > 0:
709
- observed = 'pos'
710
- else:
711
- observed = 'neg'
712
- num += 1
713
- acc_test_results.append(observed)
714
- test_results[observed].add(i)
715
- metrics_results = {}
716
- for label in labels:
717
- accuracy_score = eval_accuracy(acc_gold_results,
718
- acc_test_results)
719
- metrics_results['Accuracy'] = accuracy_score
720
- precision_score = eval_precision(gold_results[label],
721
- test_results[label])
722
- metrics_results['Precision [{0}]'.format(label)] = precision_score
723
- recall_score = eval_recall(gold_results[label],
724
- test_results[label])
725
- metrics_results['Recall [{0}]'.format(label)] = recall_score
726
- f_measure_score = eval_f_measure(gold_results[label],
727
- test_results[label])
728
- metrics_results['F-measure [{0}]'.format(label)] = f_measure_score
729
-
730
- for result in sorted(metrics_results):
731
- print('{0}: {1}'.format(result, metrics_results[result]))
732
-
733
- if output:
734
- output_markdown(output, Approach='Vader', Dataset='labeled_tweets',
735
- Instances=n_instances, Results=metrics_results)
736
-
737
- if __name__ == '__main__':
738
- from nltk.classify import NaiveBayesClassifier, MaxentClassifier
739
- from nltk.classify.scikitlearn import SklearnClassifier
740
- from sklearn.svm import LinearSVC
741
-
742
- naive_bayes = NaiveBayesClassifier.train
743
- svm = SklearnClassifier(LinearSVC()).train
744
- maxent = MaxentClassifier.train
745
-
746
- demo_tweets(naive_bayes)
747
- # demo_movie_reviews(svm)
748
- # demo_subjectivity(svm)
749
- # demo_sent_subjectivity("she's an artist , but hasn't picked up a brush in a year . ")
750
- # demo_liu_hu_lexicon("This movie was actually neither that funny, nor super witty.", plot=True)
751
- # demo_vader_instance("This movie was actually neither that funny, nor super witty.")
752
- # demo_vader_tweets()
1
+ # coding: utf-8
2
+ #
3
+ # Natural Language Toolkit: Sentiment Analyzer
4
+ #
5
+ # Copyright (C) 2001-2018 NLTK Project
6
+ # Author: Pierpaolo Pantone <24alsecondo@gmail.com>
7
+ # URL: <http://nltk.org/>
8
+ # For license information, see LICENSE.TXT
9
+
10
+ """
11
+ Utility methods for Sentiment Analysis.
12
+ """
13
+ from __future__ import division
14
+
15
+ import codecs
16
+ import csv
17
+ import json
18
+ import pickle
19
+ import random
20
+ import re
21
+ import sys
22
+ import time
23
+ from copy import deepcopy
24
+ from itertools import tee
25
+
26
+ import nltk
27
+ from nltk.corpus import CategorizedPlaintextCorpusReader
28
+ from nltk.data import load
29
+ from nltk.tokenize.casual import EMOTICON_RE
30
+ from nltk.twitter.common import outf_writer_compat, extract_fields
31
+
32
+ #////////////////////////////////////////////////////////////
33
+ #{ Regular expressions
34
+ #////////////////////////////////////////////////////////////
35
+
36
+ # Regular expression for negation by Christopher Potts
37
+ NEGATION = r"""
38
+ (?:
39
+ ^(?:never|no|nothing|nowhere|noone|none|not|
40
+ havent|hasnt|hadnt|cant|couldnt|shouldnt|
41
+ wont|wouldnt|dont|doesnt|didnt|isnt|arent|aint
42
+ )$
43
+ )
44
+ |
45
+ n't"""
46
+
47
+ NEGATION_RE = re.compile(NEGATION, re.VERBOSE)
48
+
49
+ CLAUSE_PUNCT = r'^[.:;!?]$'
50
+ CLAUSE_PUNCT_RE = re.compile(CLAUSE_PUNCT)
51
+
52
+ # Happy and sad emoticons
53
+
54
+ HAPPY = set([
55
+ ':-)', ':)', ';)', ':o)', ':]', ':3', ':c)', ':>', '=]', '8)', '=)', ':}',
56
+ ':^)', ':-D', ':D', '8-D', '8D', 'x-D', 'xD', 'X-D', 'XD', '=-D', '=D',
57
+ '=-3', '=3', ':-))', ":'-)", ":')", ':*', ':^*', '>:P', ':-P', ':P', 'X-P',
58
+ 'x-p', 'xp', 'XP', ':-p', ':p', '=p', ':-b', ':b', '>:)', '>;)', '>:-)',
59
+ '<3'
60
+ ])
61
+
62
+ SAD = set([
63
+ ':L', ':-/', '>:/', ':S', '>:[', ':@', ':-(', ':[', ':-||', '=L', ':<',
64
+ ':-[', ':-<', '=\\', '=/', '>:(', ':(', '>.<', ":'-(", ":'(", ':\\', ':-c',
65
+ ':c', ':{', '>:\\', ';('
66
+ ])
67
+
68
+
69
+ def timer(method):
70
+ """
71
+ A timer decorator to measure execution performance of methods.
72
+ """
73
+ def timed(*args, **kw):
74
+ start = time.time()
75
+ result = method(*args, **kw)
76
+ end = time.time()
77
+ tot_time = end - start
78
+ hours = tot_time // 3600
79
+ mins = tot_time // 60 % 60
80
+ # in Python 2.x round() will return a float, so we convert it to int
81
+ secs = int(round(tot_time % 60))
82
+ if hours == 0 and mins == 0 and secs < 10:
83
+ print('[TIMER] {0}(): {:.3f} seconds'.format(method.__name__, tot_time))
84
+ else:
85
+ print('[TIMER] {0}(): {1}h {2}m {3}s'.format(method.__name__, hours, mins, secs))
86
+ return result
87
+ return timed
88
+
89
+
90
+ def pairwise(iterable):
91
+ """s -> (s0,s1), (s1,s2), (s2, s3), ..."""
92
+ a, b = tee(iterable)
93
+ next(b, None)
94
+ return zip(a, b)
95
+
96
+ #////////////////////////////////////////////////////////////
97
+ #{ Feature extractor functions
98
+ #////////////////////////////////////////////////////////////
99
+ """
100
+ Feature extractor functions are declared outside the SentimentAnalyzer class.
101
+ Users should have the possibility to create their own feature extractors
102
+ without modifying SentimentAnalyzer.
103
+ """
104
+
105
+ def extract_unigram_feats(document, unigrams, handle_negation=False):
106
+ """
107
+ Populate a dictionary of unigram features, reflecting the presence/absence in
108
+ the document of each of the tokens in `unigrams`.
109
+
110
+ :param document: a list of words/tokens.
111
+ :param unigrams: a list of words/tokens whose presence/absence has to be
112
+ checked in `document`.
113
+ :param handle_negation: if `handle_negation == True` apply `mark_negation`
114
+ method to `document` before checking for unigram presence/absence.
115
+ :return: a dictionary of unigram features {unigram : boolean}.
116
+
117
+ >>> words = ['ice', 'police', 'riot']
118
+ >>> document = 'ice is melting due to global warming'.split()
119
+ >>> sorted(extract_unigram_feats(document, words).items())
120
+ [('contains(ice)', True), ('contains(police)', False), ('contains(riot)', False)]
121
+ """
122
+ features = {}
123
+ if handle_negation:
124
+ document = mark_negation(document)
125
+ for word in unigrams:
126
+ features['contains({0})'.format(word)] = word in set(document)
127
+ return features
128
+
129
+ def extract_bigram_feats(document, bigrams):
130
+ """
131
+ Populate a dictionary of bigram features, reflecting the presence/absence in
132
+ the document of each of the tokens in `bigrams`. This extractor function only
133
+ considers contiguous bigrams obtained by `nltk.bigrams`.
134
+
135
+ :param document: a list of words/tokens.
136
+ :param unigrams: a list of bigrams whose presence/absence has to be
137
+ checked in `document`.
138
+ :return: a dictionary of bigram features {bigram : boolean}.
139
+
140
+ >>> bigrams = [('global', 'warming'), ('police', 'prevented'), ('love', 'you')]
141
+ >>> document = 'ice is melting due to global warming'.split()
142
+ >>> sorted(extract_bigram_feats(document, bigrams).items())
143
+ [('contains(global - warming)', True), ('contains(love - you)', False),
144
+ ('contains(police - prevented)', False)]
145
+ """
146
+ features = {}
147
+ for bigr in bigrams:
148
+ features['contains({0} - {1})'.format(bigr[0], bigr[1])] = bigr in nltk.bigrams(document)
149
+ return features
150
+
151
+ #////////////////////////////////////////////////////////////
152
+ #{ Helper Functions
153
+ #////////////////////////////////////////////////////////////
154
+
155
+ def mark_negation(document, double_neg_flip=False, shallow=False):
156
+ """
157
+ Append _NEG suffix to words that appear in the scope between a negation
158
+ and a punctuation mark.
159
+
160
+ :param document: a list of words/tokens, or a tuple (words, label).
161
+ :param shallow: if True, the method will modify the original document in place.
162
+ :param double_neg_flip: if True, double negation is considered affirmation
163
+ (we activate/deactivate negation scope everytime we find a negation).
164
+ :return: if `shallow == True` the method will modify the original document
165
+ and return it. If `shallow == False` the method will return a modified
166
+ document, leaving the original unmodified.
167
+
168
+ >>> sent = "I didn't like this movie . It was bad .".split()
169
+ >>> mark_negation(sent)
170
+ ['I', "didn't", 'like_NEG', 'this_NEG', 'movie_NEG', '.', 'It', 'was', 'bad', '.']
171
+ """
172
+ if not shallow:
173
+ document = deepcopy(document)
174
+ # check if the document is labeled. If so, do not consider the label.
175
+ labeled = document and isinstance(document[0], (tuple, list))
176
+ if labeled:
177
+ doc = document[0]
178
+ else:
179
+ doc = document
180
+ neg_scope = False
181
+ for i, word in enumerate(doc):
182
+ if NEGATION_RE.search(word):
183
+ if not neg_scope or (neg_scope and double_neg_flip):
184
+ neg_scope = not neg_scope
185
+ continue
186
+ else:
187
+ doc[i] += '_NEG'
188
+ elif neg_scope and CLAUSE_PUNCT_RE.search(word):
189
+ neg_scope = not neg_scope
190
+ elif neg_scope and not CLAUSE_PUNCT_RE.search(word):
191
+ doc[i] += '_NEG'
192
+
193
+ return document
194
+
195
+ def output_markdown(filename, **kwargs):
196
+ """
197
+ Write the output of an analysis to a file.
198
+ """
199
+ with codecs.open(filename, 'at') as outfile:
200
+ text = '\n*** \n\n'
201
+ text += '{0} \n\n'.format(time.strftime("%d/%m/%Y, %H:%M"))
202
+ for k in sorted(kwargs):
203
+ if isinstance(kwargs[k], dict):
204
+ dictionary = kwargs[k]
205
+ text += ' - **{0}:**\n'.format(k)
206
+ for entry in sorted(dictionary):
207
+ text += ' - {0}: {1} \n'.format(entry, dictionary[entry])
208
+ elif isinstance(kwargs[k], list):
209
+ text += ' - **{0}:**\n'.format(k)
210
+ for entry in kwargs[k]:
211
+ text += ' - {0}\n'.format(entry)
212
+ else:
213
+ text += ' - **{0}:** {1} \n'.format(k, kwargs[k])
214
+ outfile.write(text)
215
+
216
+ def save_file(content, filename):
217
+ """
218
+ Store `content` in `filename`. Can be used to store a SentimentAnalyzer.
219
+ """
220
+ print("Saving", filename)
221
+ with codecs.open(filename, 'wb') as storage_file:
222
+ # The protocol=2 parameter is for python2 compatibility
223
+ pickle.dump(content, storage_file, protocol=2)
224
+
225
+ def split_train_test(all_instances, n=None):
226
+ """
227
+ Randomly split `n` instances of the dataset into train and test sets.
228
+
229
+ :param all_instances: a list of instances (e.g. documents) that will be split.
230
+ :param n: the number of instances to consider (in case we want to use only a
231
+ subset).
232
+ :return: two lists of instances. Train set is 8/10 of the total and test set
233
+ is 2/10 of the total.
234
+ """
235
+ random.seed(12345)
236
+ random.shuffle(all_instances)
237
+ if not n or n > len(all_instances):
238
+ n = len(all_instances)
239
+ train_set = all_instances[:int(.8*n)]
240
+ test_set = all_instances[int(.8*n):n]
241
+
242
+ return train_set, test_set
243
+
244
+ def _show_plot(x_values, y_values, x_labels=None, y_labels=None):
245
+ try:
246
+ import matplotlib.pyplot as plt
247
+ except ImportError:
248
+ raise ImportError('The plot function requires matplotlib to be installed.'
249
+ 'See http://matplotlib.org/')
250
+
251
+ plt.locator_params(axis='y', nbins=3)
252
+ axes = plt.axes()
253
+ axes.yaxis.grid()
254
+ plt.plot(x_values, y_values, 'ro', color='red')
255
+ plt.ylim(ymin=-1.2, ymax=1.2)
256
+ plt.tight_layout(pad=5)
257
+ if x_labels:
258
+ plt.xticks(x_values, x_labels, rotation='vertical')
259
+ if y_labels:
260
+ plt.yticks([-1, 0, 1], y_labels, rotation='horizontal')
261
+ # Pad margins so that markers are not clipped by the axes
262
+ plt.margins(0.2)
263
+ plt.show()
264
+
265
+ #////////////////////////////////////////////////////////////
266
+ #{ Parsing and conversion functions
267
+ #////////////////////////////////////////////////////////////
268
+
269
+ def json2csv_preprocess(json_file, outfile, fields, encoding='utf8', errors='replace',
270
+ gzip_compress=False, skip_retweets=True, skip_tongue_tweets=True,
271
+ skip_ambiguous_tweets=True, strip_off_emoticons=True, remove_duplicates=True,
272
+ limit=None):
273
+ """
274
+ Convert json file to csv file, preprocessing each row to obtain a suitable
275
+ dataset for tweets Semantic Analysis.
276
+
277
+ :param json_file: the original json file containing tweets.
278
+ :param outfile: the output csv filename.
279
+ :param fields: a list of fields that will be extracted from the json file and
280
+ kept in the output csv file.
281
+ :param encoding: the encoding of the files.
282
+ :param errors: the error handling strategy for the output writer.
283
+ :param gzip_compress: if True, create a compressed GZIP file.
284
+
285
+ :param skip_retweets: if True, remove retweets.
286
+ :param skip_tongue_tweets: if True, remove tweets containing ":P" and ":-P"
287
+ emoticons.
288
+ :param skip_ambiguous_tweets: if True, remove tweets containing both happy
289
+ and sad emoticons.
290
+ :param strip_off_emoticons: if True, strip off emoticons from all tweets.
291
+ :param remove_duplicates: if True, remove tweets appearing more than once.
292
+ :param limit: an integer to set the number of tweets to convert. After the
293
+ limit is reached the conversion will stop. It can be useful to create
294
+ subsets of the original tweets json data.
295
+ """
296
+ with codecs.open(json_file, encoding=encoding) as fp:
297
+ (writer, outf) = outf_writer_compat(outfile, encoding, errors, gzip_compress)
298
+ # write the list of fields as header
299
+ writer.writerow(fields)
300
+
301
+ if remove_duplicates == True:
302
+ tweets_cache = []
303
+ i = 0
304
+ for line in fp:
305
+ tweet = json.loads(line)
306
+ row = extract_fields(tweet, fields)
307
+ try:
308
+ text = row[fields.index('text')]
309
+ # Remove retweets
310
+ if skip_retweets == True:
311
+ if re.search(r'\bRT\b', text):
312
+ continue
313
+ # Remove tweets containing ":P" and ":-P" emoticons
314
+ if skip_tongue_tweets == True:
315
+ if re.search(r'\:\-?P\b', text):
316
+ continue
317
+ # Remove tweets containing both happy and sad emoticons
318
+ if skip_ambiguous_tweets == True:
319
+ all_emoticons = EMOTICON_RE.findall(text)
320
+ if all_emoticons:
321
+ if (set(all_emoticons) & HAPPY) and (set(all_emoticons) & SAD):
322
+ continue
323
+ # Strip off emoticons from all tweets
324
+ if strip_off_emoticons == True:
325
+ row[fields.index('text')] = re.sub(r'(?!\n)\s+', ' ', EMOTICON_RE.sub('', text))
326
+ # Remove duplicate tweets
327
+ if remove_duplicates == True:
328
+ if row[fields.index('text')] in tweets_cache:
329
+ continue
330
+ else:
331
+ tweets_cache.append(row[fields.index('text')])
332
+ except ValueError:
333
+ pass
334
+ writer.writerow(row)
335
+ i += 1
336
+ if limit and i >= limit:
337
+ break
338
+ outf.close()
339
+
340
+ def parse_tweets_set(filename, label, word_tokenizer=None, sent_tokenizer=None,
341
+ skip_header=True):
342
+ """
343
+ Parse csv file containing tweets and output data a list of (text, label) tuples.
344
+
345
+ :param filename: the input csv filename.
346
+ :param label: the label to be appended to each tweet contained in the csv file.
347
+ :param word_tokenizer: the tokenizer instance that will be used to tokenize
348
+ each sentence into tokens (e.g. WordPunctTokenizer() or BlanklineTokenizer()).
349
+ If no word_tokenizer is specified, tweets will not be tokenized.
350
+ :param sent_tokenizer: the tokenizer that will be used to split each tweet into
351
+ sentences.
352
+ :param skip_header: if True, skip the first line of the csv file (which usually
353
+ contains headers).
354
+
355
+ :return: a list of (text, label) tuples.
356
+ """
357
+ tweets = []
358
+ if not sent_tokenizer:
359
+ sent_tokenizer = load('tokenizers/punkt/english.pickle')
360
+
361
+ # If we use Python3.x we can proceed using the 'rt' flag
362
+ if sys.version_info[0] == 3:
363
+ with codecs.open(filename, 'rt') as csvfile:
364
+ reader = csv.reader(csvfile)
365
+ if skip_header == True:
366
+ next(reader, None) # skip the header
367
+ i = 0
368
+ for tweet_id, text in reader:
369
+ # text = text[1]
370
+ i += 1
371
+ sys.stdout.write('Loaded {0} tweets\r'.format(i))
372
+ # Apply sentence and word tokenizer to text
373
+ if word_tokenizer:
374
+ tweet = [w for sent in sent_tokenizer.tokenize(text)
375
+ for w in word_tokenizer.tokenize(sent)]
376
+ else:
377
+ tweet = text
378
+ tweets.append((tweet, label))
379
+ # If we use Python2.x we need to handle encoding problems
380
+ elif sys.version_info[0] < 3:
381
+ with codecs.open(filename) as csvfile:
382
+ reader = csv.reader(csvfile)
383
+ if skip_header == True:
384
+ next(reader, None) # skip the header
385
+ i = 0
386
+ for row in reader:
387
+ unicode_row = [x.decode('utf8') for x in row]
388
+ text = unicode_row[1]
389
+ i += 1
390
+ sys.stdout.write('Loaded {0} tweets\r'.format(i))
391
+ # Apply sentence and word tokenizer to text
392
+ if word_tokenizer:
393
+ tweet = [w.encode('utf8') for sent in sent_tokenizer.tokenize(text)
394
+ for w in word_tokenizer.tokenize(sent)]
395
+ else:
396
+ tweet = text
397
+ tweets.append((tweet, label))
398
+ print("Loaded {0} tweets".format(i))
399
+ return tweets
400
+
401
+ #////////////////////////////////////////////////////////////
402
+ #{ Demos
403
+ #////////////////////////////////////////////////////////////
404
+
405
+ def demo_tweets(trainer, n_instances=None, output=None):
406
+ """
407
+ Train and test Naive Bayes classifier on 10000 tweets, tokenized using
408
+ TweetTokenizer.
409
+ Features are composed of:
410
+ - 1000 most frequent unigrams
411
+ - 100 top bigrams (using BigramAssocMeasures.pmi)
412
+
413
+ :param trainer: `train` method of a classifier.
414
+ :param n_instances: the number of total tweets that have to be used for
415
+ training and testing. Tweets will be equally split between positive and
416
+ negative.
417
+ :param output: the output file where results have to be reported.
418
+ """
419
+ from nltk.tokenize import TweetTokenizer
420
+ from nltk.sentiment import SentimentAnalyzer
421
+ from nltk.corpus import twitter_samples, stopwords
422
+
423
+ # Different customizations for the TweetTokenizer
424
+ tokenizer = TweetTokenizer(preserve_case=False)
425
+ # tokenizer = TweetTokenizer(preserve_case=True, strip_handles=True)
426
+ # tokenizer = TweetTokenizer(reduce_len=True, strip_handles=True)
427
+
428
+ if n_instances is not None:
429
+ n_instances = int(n_instances/2)
430
+
431
+ fields = ['id', 'text']
432
+ positive_json = twitter_samples.abspath("positive_tweets.json")
433
+ positive_csv = 'positive_tweets.csv'
434
+ json2csv_preprocess(positive_json, positive_csv, fields, limit=n_instances)
435
+
436
+ negative_json = twitter_samples.abspath("negative_tweets.json")
437
+ negative_csv = 'negative_tweets.csv'
438
+ json2csv_preprocess(negative_json, negative_csv, fields, limit=n_instances)
439
+
440
+ neg_docs = parse_tweets_set(negative_csv, label='neg', word_tokenizer=tokenizer)
441
+ pos_docs = parse_tweets_set(positive_csv, label='pos', word_tokenizer=tokenizer)
442
+
443
+ # We separately split subjective and objective instances to keep a balanced
444
+ # uniform class distribution in both train and test sets.
445
+ train_pos_docs, test_pos_docs = split_train_test(pos_docs)
446
+ train_neg_docs, test_neg_docs = split_train_test(neg_docs)
447
+
448
+ training_tweets = train_pos_docs+train_neg_docs
449
+ testing_tweets = test_pos_docs+test_neg_docs
450
+
451
+ sentim_analyzer = SentimentAnalyzer()
452
+ # stopwords = stopwords.words('english')
453
+ # all_words = [word for word in sentim_analyzer.all_words(training_tweets) if word.lower() not in stopwords]
454
+ all_words = [word for word in sentim_analyzer.all_words(training_tweets)]
455
+
456
+ # Add simple unigram word features
457
+ unigram_feats = sentim_analyzer.unigram_word_feats(all_words, top_n=1000)
458
+ sentim_analyzer.add_feat_extractor(extract_unigram_feats, unigrams=unigram_feats)
459
+
460
+ # Add bigram collocation features
461
+ bigram_collocs_feats = sentim_analyzer.bigram_collocation_feats([tweet[0] for tweet in training_tweets],
462
+ top_n=100, min_freq=12)
463
+ sentim_analyzer.add_feat_extractor(extract_bigram_feats, bigrams=bigram_collocs_feats)
464
+
465
+ training_set = sentim_analyzer.apply_features(training_tweets)
466
+ test_set = sentim_analyzer.apply_features(testing_tweets)
467
+
468
+ classifier = sentim_analyzer.train(trainer, training_set)
469
+ # classifier = sentim_analyzer.train(trainer, training_set, max_iter=4)
470
+ try:
471
+ classifier.show_most_informative_features()
472
+ except AttributeError:
473
+ print('Your classifier does not provide a show_most_informative_features() method.')
474
+ results = sentim_analyzer.evaluate(test_set)
475
+
476
+ if output:
477
+ extr = [f.__name__ for f in sentim_analyzer.feat_extractors]
478
+ output_markdown(output, Dataset='labeled_tweets', Classifier=type(classifier).__name__,
479
+ Tokenizer=tokenizer.__class__.__name__, Feats=extr,
480
+ Results=results, Instances=n_instances)
481
+
482
+ def demo_movie_reviews(trainer, n_instances=None, output=None):
483
+ """
484
+ Train classifier on all instances of the Movie Reviews dataset.
485
+ The corpus has been preprocessed using the default sentence tokenizer and
486
+ WordPunctTokenizer.
487
+ Features are composed of:
488
+ - most frequent unigrams
489
+
490
+ :param trainer: `train` method of a classifier.
491
+ :param n_instances: the number of total reviews that have to be used for
492
+ training and testing. Reviews will be equally split between positive and
493
+ negative.
494
+ :param output: the output file where results have to be reported.
495
+ """
496
+ from nltk.corpus import movie_reviews
497
+ from nltk.sentiment import SentimentAnalyzer
498
+
499
+ if n_instances is not None:
500
+ n_instances = int(n_instances/2)
501
+
502
+ pos_docs = [(list(movie_reviews.words(pos_id)), 'pos') for pos_id in movie_reviews.fileids('pos')[:n_instances]]
503
+ neg_docs = [(list(movie_reviews.words(neg_id)), 'neg') for neg_id in movie_reviews.fileids('neg')[:n_instances]]
504
+ # We separately split positive and negative instances to keep a balanced
505
+ # uniform class distribution in both train and test sets.
506
+ train_pos_docs, test_pos_docs = split_train_test(pos_docs)
507
+ train_neg_docs, test_neg_docs = split_train_test(neg_docs)
508
+
509
+ training_docs = train_pos_docs+train_neg_docs
510
+ testing_docs = test_pos_docs+test_neg_docs
511
+
512
+ sentim_analyzer = SentimentAnalyzer()
513
+ all_words = sentim_analyzer.all_words(training_docs)
514
+
515
+ # Add simple unigram word features
516
+ unigram_feats = sentim_analyzer.unigram_word_feats(all_words, min_freq=4)
517
+ sentim_analyzer.add_feat_extractor(extract_unigram_feats, unigrams=unigram_feats)
518
+ # Apply features to obtain a feature-value representation of our datasets
519
+ training_set = sentim_analyzer.apply_features(training_docs)
520
+ test_set = sentim_analyzer.apply_features(testing_docs)
521
+
522
+ classifier = sentim_analyzer.train(trainer, training_set)
523
+ try:
524
+ classifier.show_most_informative_features()
525
+ except AttributeError:
526
+ print('Your classifier does not provide a show_most_informative_features() method.')
527
+ results = sentim_analyzer.evaluate(test_set)
528
+
529
+ if output:
530
+ extr = [f.__name__ for f in sentim_analyzer.feat_extractors]
531
+ output_markdown(output, Dataset='Movie_reviews', Classifier=type(classifier).__name__,
532
+ Tokenizer='WordPunctTokenizer', Feats=extr, Results=results,
533
+ Instances=n_instances)
534
+
535
+ def demo_subjectivity(trainer, save_analyzer=False, n_instances=None, output=None):
536
+ """
537
+ Train and test a classifier on instances of the Subjective Dataset by Pang and
538
+ Lee. The dataset is made of 5000 subjective and 5000 objective sentences.
539
+ All tokens (words and punctuation marks) are separated by a whitespace, so
540
+ we use the basic WhitespaceTokenizer to parse the data.
541
+
542
+ :param trainer: `train` method of a classifier.
543
+ :param save_analyzer: if `True`, store the SentimentAnalyzer in a pickle file.
544
+ :param n_instances: the number of total sentences that have to be used for
545
+ training and testing. Sentences will be equally split between positive
546
+ and negative.
547
+ :param output: the output file where results have to be reported.
548
+ """
549
+ from nltk.sentiment import SentimentAnalyzer
550
+ from nltk.corpus import subjectivity
551
+
552
+ if n_instances is not None:
553
+ n_instances = int(n_instances/2)
554
+
555
+ subj_docs = [(sent, 'subj') for sent in subjectivity.sents(categories='subj')[:n_instances]]
556
+ obj_docs = [(sent, 'obj') for sent in subjectivity.sents(categories='obj')[:n_instances]]
557
+
558
+ # We separately split subjective and objective instances to keep a balanced
559
+ # uniform class distribution in both train and test sets.
560
+ train_subj_docs, test_subj_docs = split_train_test(subj_docs)
561
+ train_obj_docs, test_obj_docs = split_train_test(obj_docs)
562
+
563
+ training_docs = train_subj_docs+train_obj_docs
564
+ testing_docs = test_subj_docs+test_obj_docs
565
+
566
+ sentim_analyzer = SentimentAnalyzer()
567
+ all_words_neg = sentim_analyzer.all_words([mark_negation(doc) for doc in training_docs])
568
+
569
+ # Add simple unigram word features handling negation
570
+ unigram_feats = sentim_analyzer.unigram_word_feats(all_words_neg, min_freq=4)
571
+ sentim_analyzer.add_feat_extractor(extract_unigram_feats, unigrams=unigram_feats)
572
+
573
+ # Apply features to obtain a feature-value representation of our datasets
574
+ training_set = sentim_analyzer.apply_features(training_docs)
575
+ test_set = sentim_analyzer.apply_features(testing_docs)
576
+
577
+ classifier = sentim_analyzer.train(trainer, training_set)
578
+ try:
579
+ classifier.show_most_informative_features()
580
+ except AttributeError:
581
+ print('Your classifier does not provide a show_most_informative_features() method.')
582
+ results = sentim_analyzer.evaluate(test_set)
583
+
584
+ if save_analyzer == True:
585
+ save_file(sentim_analyzer, 'sa_subjectivity.pickle')
586
+
587
+ if output:
588
+ extr = [f.__name__ for f in sentim_analyzer.feat_extractors]
589
+ output_markdown(output, Dataset='subjectivity', Classifier=type(classifier).__name__,
590
+ Tokenizer='WhitespaceTokenizer', Feats=extr,
591
+ Instances=n_instances, Results=results)
592
+
593
+ return sentim_analyzer
594
+
595
+ def demo_sent_subjectivity(text):
596
+ """
597
+ Classify a single sentence as subjective or objective using a stored
598
+ SentimentAnalyzer.
599
+
600
+ :param text: a sentence whose subjectivity has to be classified.
601
+ """
602
+ from nltk.classify import NaiveBayesClassifier
603
+ from nltk.tokenize import regexp
604
+ word_tokenizer = regexp.WhitespaceTokenizer()
605
+ try:
606
+ sentim_analyzer = load('sa_subjectivity.pickle')
607
+ except LookupError:
608
+ print('Cannot find the sentiment analyzer you want to load.')
609
+ print('Training a new one using NaiveBayesClassifier.')
610
+ sentim_analyzer = demo_subjectivity(NaiveBayesClassifier.train, True)
611
+
612
+ # Tokenize and convert to lower case
613
+ tokenized_text = [word.lower() for word in word_tokenizer.tokenize(text)]
614
+ print(sentim_analyzer.classify(tokenized_text))
615
+
616
+ def demo_liu_hu_lexicon(sentence, plot=False):
617
+ """
618
+ Basic example of sentiment classification using Liu and Hu opinion lexicon.
619
+ This function simply counts the number of positive, negative and neutral words
620
+ in the sentence and classifies it depending on which polarity is more represented.
621
+ Words that do not appear in the lexicon are considered as neutral.
622
+
623
+ :param sentence: a sentence whose polarity has to be classified.
624
+ :param plot: if True, plot a visual representation of the sentence polarity.
625
+ """
626
+ from nltk.corpus import opinion_lexicon
627
+ from nltk.tokenize import treebank
628
+
629
+ tokenizer = treebank.TreebankWordTokenizer()
630
+ pos_words = 0
631
+ neg_words = 0
632
+ tokenized_sent = [word.lower() for word in tokenizer.tokenize(sentence)]
633
+
634
+ x = list(range(len(tokenized_sent))) # x axis for the plot
635
+ y = []
636
+
637
+ for word in tokenized_sent:
638
+ if word in opinion_lexicon.positive():
639
+ pos_words += 1
640
+ y.append(1) # positive
641
+ elif word in opinion_lexicon.negative():
642
+ neg_words += 1
643
+ y.append(-1) # negative
644
+ else:
645
+ y.append(0) # neutral
646
+
647
+ if pos_words > neg_words:
648
+ print('Positive')
649
+ elif pos_words < neg_words:
650
+ print('Negative')
651
+ elif pos_words == neg_words:
652
+ print('Neutral')
653
+
654
+ if plot == True:
655
+ _show_plot(x, y, x_labels=tokenized_sent, y_labels=['Negative', 'Neutral', 'Positive'])
656
+
657
+ def demo_vader_instance(text):
658
+ """
659
+ Output polarity scores for a text using Vader approach.
660
+
661
+ :param text: a text whose polarity has to be evaluated.
662
+ """
663
+ from nltk.sentiment import SentimentIntensityAnalyzer
664
+ vader_analyzer = SentimentIntensityAnalyzer()
665
+ print(vader_analyzer.polarity_scores(text))
666
+
667
+ def demo_vader_tweets(n_instances=None, output=None):
668
+ """
669
+ Classify 10000 positive and negative tweets using Vader approach.
670
+
671
+ :param n_instances: the number of total tweets that have to be classified.
672
+ :param output: the output file where results have to be reported.
673
+ """
674
+ from collections import defaultdict
675
+ from nltk.corpus import twitter_samples
676
+ from nltk.sentiment import SentimentIntensityAnalyzer
677
+ from nltk.metrics import (accuracy as eval_accuracy, precision as eval_precision,
678
+ recall as eval_recall, f_measure as eval_f_measure)
679
+
680
+ if n_instances is not None:
681
+ n_instances = int(n_instances/2)
682
+
683
+ fields = ['id', 'text']
684
+ positive_json = twitter_samples.abspath("positive_tweets.json")
685
+ positive_csv = 'positive_tweets.csv'
686
+ json2csv_preprocess(positive_json, positive_csv, fields, strip_off_emoticons=False,
687
+ limit=n_instances)
688
+
689
+ negative_json = twitter_samples.abspath("negative_tweets.json")
690
+ negative_csv = 'negative_tweets.csv'
691
+ json2csv_preprocess(negative_json, negative_csv, fields, strip_off_emoticons=False,
692
+ limit=n_instances)
693
+
694
+ pos_docs = parse_tweets_set(positive_csv, label='pos')
695
+ neg_docs = parse_tweets_set(negative_csv, label='neg')
696
+
697
+ # We separately split subjective and objective instances to keep a balanced
698
+ # uniform class distribution in both train and test sets.
699
+ train_pos_docs, test_pos_docs = split_train_test(pos_docs)
700
+ train_neg_docs, test_neg_docs = split_train_test(neg_docs)
701
+
702
+ training_tweets = train_pos_docs+train_neg_docs
703
+ testing_tweets = test_pos_docs+test_neg_docs
704
+
705
+ vader_analyzer = SentimentIntensityAnalyzer()
706
+
707
+ gold_results = defaultdict(set)
708
+ test_results = defaultdict(set)
709
+ acc_gold_results = []
710
+ acc_test_results = []
711
+ labels = set()
712
+ num = 0
713
+ for i, (text, label) in enumerate(testing_tweets):
714
+ labels.add(label)
715
+ gold_results[label].add(i)
716
+ acc_gold_results.append(label)
717
+ score = vader_analyzer.polarity_scores(text)['compound']
718
+ if score > 0:
719
+ observed = 'pos'
720
+ else:
721
+ observed = 'neg'
722
+ num += 1
723
+ acc_test_results.append(observed)
724
+ test_results[observed].add(i)
725
+ metrics_results = {}
726
+ for label in labels:
727
+ accuracy_score = eval_accuracy(acc_gold_results,
728
+ acc_test_results)
729
+ metrics_results['Accuracy'] = accuracy_score
730
+ precision_score = eval_precision(gold_results[label],
731
+ test_results[label])
732
+ metrics_results['Precision [{0}]'.format(label)] = precision_score
733
+ recall_score = eval_recall(gold_results[label],
734
+ test_results[label])
735
+ metrics_results['Recall [{0}]'.format(label)] = recall_score
736
+ f_measure_score = eval_f_measure(gold_results[label],
737
+ test_results[label])
738
+ metrics_results['F-measure [{0}]'.format(label)] = f_measure_score
739
+
740
+ for result in sorted(metrics_results):
741
+ print('{0}: {1}'.format(result, metrics_results[result]))
742
+
743
+ if output:
744
+ output_markdown(output, Approach='Vader', Dataset='labeled_tweets',
745
+ Instances=n_instances, Results=metrics_results)
746
+
747
+ if __name__ == '__main__':
748
+ from nltk.classify import NaiveBayesClassifier, MaxentClassifier
749
+ from nltk.classify.scikitlearn import SklearnClassifier
750
+ from sklearn.svm import LinearSVC
751
+
752
+ naive_bayes = NaiveBayesClassifier.train
753
+ svm = SklearnClassifier(LinearSVC()).train
754
+ maxent = MaxentClassifier.train
755
+
756
+ demo_tweets(naive_bayes)
757
+ # demo_movie_reviews(svm)
758
+ # demo_subjectivity(svm)
759
+ # demo_sent_subjectivity("she's an artist , but hasn't picked up a brush in a year . ")
760
+ # demo_liu_hu_lexicon("This movie was actually neither that funny, nor super witty.", plot=True)
761
+ # demo_vader_instance("This movie was actually neither that funny, nor super witty.")
762
+ # demo_vader_tweets()