Package: qcrit
Easily extract features from texts, and run machine learning algorithms on them. Write your own features, use ours, or do both!https://www.qcrit.orgInstallationWithpip:pipinstallqcritWithpipenvpipenvinstallqcritAboutThe qcrit package contains utilities to facilitate processing and analyzing literature.To try extracting some features, just replace'your-directory-name'with the name of a directory of.txtfiles. Everything else is taken care of!fromqcrit.extract_featuresimportmainfromqcrit.textual_featureimportsetup_tokenizersimportqcrit.features.universal_featuressetup_tokenizers(terminal_punctuation=('.','!','?'))main(corpus_dir='your-directory-name',file_extension_to_parse_function={'txt':lambdafilename:open(filename).read()})Writing Your Own FeaturesA feature is a number that results from processing literature. An example of a feature might be the number of definite articles, the mean sentence length, or the fraction of interrogative sentences. The word "feature" can also refer to a python function that computes such a value.Normally to compute features, you must 1) obtain a corpus of texts, 2) traverse each text in the corpus, 3) parse the text into tokens, 4) write logic to calculate features, and 5) output the results to the console or to a file. Also, this will run slowly unless you 6) cache tokenized text for features that use the same tokens.With thetextual_featuredecorator, steps (2), (3), (5), and (6) are abstracted away - you just need (1) to supply the corpus.Once you have written a feature as apythonfunction, label it with the decoratortextual_feature. Your feature must have exactly one parameter which is assumed to be the parsed text of a file.fromqcrit.textual_featureimporttextual_feature@textual_feature()defcount_definite_article(text):returntext.count('the')Thetextual_featuredecorator takes an argument that represents the type of tokenization.There are four supported tokenization_types:'sentences','words','sentence_words'andNone. This tells the function in
what format it will receive the'text'parameter.IfNone, the function will receive the text parameter as a string.If'sentences', the function will receive the text parameter as a list of sentences, each as a stringIf'words', the function will receive the text parameter as a list of wordsIf'sentence_words', the function will recieve the text parameter as a list of sentences, each as a list of wordsfromfunctoolsimportreduce@textual_feature(tokenize_type='sentences')defmean_sentence_len(text):sen_len=reduce(lambdacur_len,cur_sen:cur_len+len(cur_sen),text,0)num_sentences=len(text)returnsen_len/num_sentencesExtracting FeaturesUseqcrit.extract_features.mainto run all the functions labeled with the@textual_featuredecorator and output results into a file.corpus_dir- the directory to search for files containing texts, this will traverse all sub-directories as wellfile_extension_to_parse_function- map from file extension (e.g. 'txt', 'tess') of texts that you would like to parse to a function directing how to parse itoutput_file- the file to output the results into, created to be analyzed during machine learning phaseIn order for sentence tokenization to work correctly,setup_tokenizers()must be called with the
terminal punctuation marks of the language being analyzed. You can also optionally supply the name of the language as well. If data exists about how to parse the language, this may improve sentence tokenization.fromqcrit.extract_featuresimportmain,parse_tessfromqcrit.textual_featureimportsetup_tokenizersfromsomewhere_elseimportcount_definite_article,mean_sentence_lensetup_tokenizers(terminal_punctuation=('.','!','?'),language='greek')main(corpus_dir='demo',file_extension_to_parse_function={'tess':parse_tess},output_file='output.pickle')Output:Extractingfeaturesfrom.tessfilesindemo/100%|██████████████████████████████████████████|4/4[00:00<00:00,8.67it/s]Featureminingcomplete.Attemptingtowritefeatureresultsto"output.pickle"...
Success!


Featureminingelapsedtime:1.4919secondsAnalysisUse the@model_analyzer()decorator to label functions that analyze machine learning modelsInvokeanalyze_models.main('output.pickle', 'classifications.csv')to
run all functions labeled with the@model_analyzer()decorator. To run only one function, include
the name of the function as the third parameter to analyze_models.main()output.pickle: Now that the features have been extracted and output into output.pickle, we
can use machine learning models on them.classifications.csv: The file classifications.csv contains the name of the file in the first column
and the particular classification (prose or verse) in the second column for every file in the corpus.importqcrit.analyze_modelsfromqcrit.model_analyzerimportmodel_analyzerfromsklearnimportensemblefromsklearn.model_selectionimporttrain_test_splitfromsklearn.metricsimportaccuracy_score@model_analyzer()deffeature_rankings(data,target,file_names,feature_names,labels_key):print('-'*40+'\nRandom Forest Classifier feature rankings\n')features_train,features_test,labels_train,_=train_test_split(data,target,test_size=0.5,random_state=0)clf=ensemble.RandomForestClassifier(random_state=0,n_estimators=10)clf.fit(features_train,labels_train)clf.predict(features_test)#Display features in order of importanceprint('Feature importances:')fortupinsorted(zip(feature_names,clf.feature_importances_),key=lambdas:-s[1]):print('\t%f:%s'%(tup[1],tup[0]))@model_analyzer()defclassifier_accuracy(data,target,file_names,feature_names,labels_key):print('-'*40+'\nRandom Forest Classifier accuracy\n')features_train,features_test,labels_train,labels_test=train_test_split(data,target,test_size=0.5,random_state=0)clf=ensemble.RandomForestClassifier(random_state=0,n_estimators=10)clf.fit(features_train,labels_train)results=clf.predict(features_test)print('Stats:')print('\tNumber correct: '+str(accuracy_score(labels_test,results,normalize=False))+' / '+str(len(results)))print('\tPercentage correct: '+str(accuracy_score(labels_test,results)*100)+'%')@model_analyzer()defmisclassified_texts(data,target,file_names,feature_names,labels_key):print('-'*40+'\nRandom Forest Classifier misclassified texts\n')features_train,features_test,labels_train,labels_test,idx_train,idx_test=train_test_split(data,target,range(len(target)),test_size=0.5,random_state=0)print('Train texts:\n\t'+'\n\t'.join(file_names[i]foriinidx_train)+'\n')print('Test texts:\n\t'+'\n\t'.join(file_names[i]foriinidx_test)+'\n')clf=ensemble.RandomForestClassifier(random_state=0,n_estimators=10)clf.fit(features_train,labels_train)results=clf.predict(features_test)print('Misclassifications:')fori,_inenumerate(results):ifresults[i]!=labels_test[i]:print('\t'+file_names[idx_test[i]])qcrit.analyze_models.main('output.pickle','classifications.csv')Output:----------------------------------------
Random Forest Classifier feature rankings

Feature importances:
	0.400000: num_conjunctions
	0.400000: num_interrogatives
	0.200000: mean_sentence_length


Elapsed time: 0.0122 seconds

----------------------------------------
Random Forest Classifier accuracy

Stats:
	Number correct: 1 / 2
	Percentage correct: 50.0%


Elapsed time: 0.0085 seconds

----------------------------------------
Random Forest Classifier misclassified texts

Train texts:
	demo/aristotle.poetics.tess
	demo/aristophanes.ecclesiazusae.tess

Test texts:
	demo/euripides.heracles.tess
	demo/plato.respublica.part.1.tess

Misclassifications:
	demo/plato.respublica.part.1.tess


Elapsed time: 0.0082 secondsDevelopmentEnsure that you havepipenvinstalled. Also, ensure that you have a version ofpythoninstalled that matches the version in thePipfile.Setup a virtual environment and install the necessary dependencies:PIPENV_VENV_IN_PROJECT=truepipenvinstall--devActivate the virtual environment:pipenvshellNow,pythoncommands will use the dependencies andpythonversion from the virtual environment. Useexitto leave the virtual environment, and usepipenv shellwhile in the project directory to activate it again.Demopythondemo/demo.pySubmissionThe following commands will submit the package to thePython Package Index. Before running them, it may be necessary to increment the version number in__init__.pyand to delete any previously generateddist/,build/, andegg-infodirectories if they exist.pythonsetup.pybdist_wheelsdist
twineuploaddist/*