Package: buzz
buzz: python corpus linguisticsVersion 3.1.8buzzis a linguistics tool for parsing and then exploring plain or metadata-rich text. This README provides an overview of functionality. Visit thefull documentationfor a more complete user guide.Installbuzzrequires Python 3.6 or higher. A virtual environment is recommended.pipinstallbuzz[word]# orgitclonehttp://github.com/interrogator/buzzcdbuzz
pythonsetup.pyinstallFrontend:buzzwordbuzzhas an optional frontend,buzzword, for exploring parsed corpora. To use it, install:pipinstallbuzz[word]Then, generate a workspace,cdinto it, and start:python-mbuzzword.createworkspacecdworkspace
python-mbuzzwordMore complete documentation is availablehere, as well from the main page of the app itself.A URL will be printed, which can be used to access the app in your browser.Creating corporabuzzmodels plain text, orCONLL-U formattedfiles. The remainder of this guide will assume that you are have plain text data, and want to process and analyse it on the command line usingbuzz.First, you need to make sure that your corpus is in a format and structure thatbuzzcan work with. This simply means putting all your text files into a folder, and optionally within subfolders (representing subcorpora).Text files should be plain text, with a.txtextension. Importantly though, they can be augmented with metadata, which can be stored in two ways. First, speaker names can be added by using capital letters and a colon, much like in a script. Second, you can use XML style metadata markup. Here is an example file,sopranos/s1/e01.txt:<metaaired="10.01.1999"/>MELFI: My understanding from Dr. Cusamano, your family physician, is you collapsed? Possibly a panic attack?<metaexposition=trueinterrogative-type="intonation"move="info-request">TONY:<metaemph=true>They</meta>said it was a panic attack<metamove="refute"/>MELFI: You don't agree that you had a panic attack?<metamove="info-request"question=type="in"/>...If you add ametaelement at the start of the text file, it will be understood as file-level metadata. For sentence-specific metadata, the element should follow the sentence, ideally at the end of a line. Span- and token-level metadata should wrap the tokens you want to annotate. All metadata will be searchable later, so the more you can add, the more you can do with your corpus.To load corpora asbuzzobjects:frombuzzimportCorpuscorpus=Corpus("sopranos")You can also make virtual corpora from strings, optionally saving the corpus to disk.corpus=Corpus.from_string("Some sentences here.",save_as="corpusname")Parsingbuzz usesspaCyto parse your text, saving the results as CONLL-U files to your hard drive. Parsing by default is only for dependencies, but constituency parsing can be added with a keyword argument:# only dependency parsingparsed=corpus.parse()# if you also want constituency parsing, using beneparparsed=corpus.parse(cons_parser="benepar")# if you want constituency parsing using bllipparsed=corpus.parse(cons_parser="bblip")You can also parse text strings, optionally passing in a name under which to save the corpus:frombuzzimportParserparser=Parser(cons_parser="benepar")fortextinlist_of_texts:dataset=parser.run(text,save_as=False)The main advantages of parsing withbuzzare that:Parse results are stored as valid CONLL-U 2.0Metadata is respected, and transferred into the output filesYou can do constituency and dependency parsing at the same time (with parse trees being stored as CONLL-U metadata)theparse()method returns anotherCorpusobject, representing the newly created files. We can explore this corpus via commands like:parsed.subcorpora.s1.files.e01parsed.files[0]parsed.subcorpora.s1[:5]parsed.subcorpora["s1"]Parse commandYou can also parse corpora without entering a Python session by using theparsecommand:parse--languageen--cons-parser=benepar|bllip|nonepath/to/conll/files# orpython-mbuzz.parsepath/to/conll/filesBoth commands will createpath/to/conll/files-parsed, a folder containing CONLL-U files.Loading corpora into memoryYou can use theload()method to load a whole or partial corpus into memory, as a Dataset object, which extends thepandas DataFrame.loaded=parsed.load()You don't need to load corpora into memory to work on them, but it's great for small corpora. As a rule of thumb, datasets under a million words should be easily loadable on a personal computer.The loaded corpus is aDatasetobject, which is based on the pandas DataFrame. So, you can use pandas methods on it:loaded.head()wlxpgfeairedemphent_ident_iobent_typeexpositioninterrogative_typemovequestionsent_idsent_lenspeakertext_nfilesitext11My-PRON-DETPRP$2poss_10.01.1999_2O_Trueintonationinfo-request_114MELFIMy understanding from Dr. Cusamano, your family physician, is you collapsed?02understandingunderstandingNOUNNN13nsubjpass_10.01.1999_2O_Trueintonationinfo-request_114MELFIMy understanding from Dr. Cusamano, your family physician, is you collapsed?13fromfromADPIN2prep_10.01.1999_2O_Trueintonationinfo-request_114MELFIMy understanding from Dr. Cusamano, your family physician, is you collapsed?24Dr.Dr.PROPNNNP5compound_10.01.1999_2O_Trueintonationinfo-request_114MELFIMy understanding from Dr. Cusamano, your family physician, is you collapsed?35CusamanoCusamanoPROPNNNP3pobj_10.01.1999_3BPERSONTrueintonationinfo-request_114MELFIMy understanding from Dr. Cusamano, your family physician, is you collapsed?4You can also interactively explore the corpus withtabviewusing theview()method:loaded.view()The interactive view has a number of cool features, such as the ability to sort by row or column. Also, pressingenteron a given line will generate a concordance based on that line's contents. Neat!Exploring parsed and loaded corporaA corpus is a pandas DataFrame object. The index is a multiindex, comprised offilename,sent_idandtoken. Each token in the corpus is therefore uniquely identifiable through this index. The columns for the loaded copus are all the CONLL columns, plus anything included as metadata.# get the first sentence using buzz.dataset.sent()first=loaded.sent(0)# using pandas syntax to get first 5 wordsfirst.iloc[:5]["w"]# join the wordclasses and wordsprint(" ".join(first.x.str.cat(first.w,sep="/")))"DET/My NOUN/understanding ADP/from PROPN/Dr. PROPN/Cusamano PUNCT/, DET/your NOUN/family NOUN/physician PUNCT/, VERB/is PRON/you VERB/collapsed PUNCT/?You don't need to know pandas, however, in order to usebuzz, becausebuzzmakes possible some more intuitive measures with linguistics in mind. For example, if you want to slice the corpus some way, you can easily do this using thejustandskipproperties, combined with the column/metadata feature you want to filter by:tony=loaded.just.speaker.TONY# you can use brackets (i.e. for regular expressions):no_punct=loaded.skip.lemmata("^[^a-zA-Z0-9]")# or you can pass in a list/set/tuple:end_in_s=loaded.just.pos(["NNS","NNPS","VBZ"])Any object created bybuzzhas a.view()method, which launches atabviewinteractive space where you can explore corpora, frequencies or concordances.spaCyspaCyis used under the hood for dependency parsing, and a couple of other things. spaCy bring with it a lot of state of the art methods in NLP. You can access thespaCyrepresentation of your data with:corpus.to_spacy()# orloaded.to_spacy()Searching dependenciesTo search the dependency graph generated by spaCy during parsing, you can use thedepgrepmethod.# search dependencies for nominal subjects with definite articlesnsubj=loaded.depgrep('f/nsubj.*/ -> (w"the" & x"DET")')The search language works by modelling nodes and the links between them. Specifying a node, likef/nsubj/, is done by specifying the feature you want to match (fforfunction), and a query inside slashes (for regular expressions) or inside quotation marks (for literal matches).The arrow-like link specifies that thensubjmust govern the determiner. The&relation specifies that the two nodes are actually the same node. Brackets may be necessary to contain the query.This language is based onTgrep2, syntax, customised for dependencies. It is still a work in progress, but documentation should emergehere, with repositoryhere.Drill-downWhen you search aCorpusorDataset, the result is simply another Dataset, representing a subset of the Corpus. Therefore, rather than trying to construct one query string that gets everything you want, it is often easier to perform multiple small searches:query='f/nsubj/ <- f/ROOT/'tony_subjects=loaded.skip.wordclass.PUNCT.just.speaker.TONY.depgrep(query)Note that for any searches that do not require traversal of the grammatical structure, you should use theskipandjustmethods.tgrepanddepgreponly need to be used when your search involves the grammar, and not just token features.Searching constituency treesThis is deprecated right now, due to lack of use (combined with requiring a lot of special handling). Make an issue if you really need this functionality and we can consider bringing it back, probably via BLLIP or Benepar. If you're making corpora with constituency parses, please useparse = (S ...)as sentence-level metadata to encode the parse.Viewing search resultsAn important principle inbuzzis the separation of searching and viewing results. Unlike many other tools, you do not search for a concordance---instead, you search the corpus, and then visualise the output of the data as a concordance.ConcordancingConcordancing is a nice way of looking at results. The main thing you have to do is tellbuzzhow you want the match column to look---it can be just the matching words, but also any combination of things. To show words and their parts of speech, you can do:nsubj=loaded.just.function.nsubjnsubj.conc(show=["w","p"])Frequency tablesYou can turn your dataset into frequency tables, both before or after searching or filtering. Tabling takes ashowargument similar to theshowargument for concordancing, as well as an additionalsubcorporaargument.showrepresents the how the columns will be formatted, andsubcorporais used as the index. Below we create a frequency table ofnsubjtokens, in lemma form, organised by speaker.tab=nsubj.table(show="l",subcorpora=["speaker"])Possible keyword arguments for the.table()method are as follows:ArgumentDescriptionDefaultsubcorporaFeature(s) to use as the index of the table. Passing in a list of multiple features will create a multiindex['file']showFeature(s) to use as the columns of the table. Passing a list will join the features with slash, so['w', 'p']results in columns with names like'friend/NN'['w']sortHow to sort the results. 'total'/'infreq', 'increase/'decrease', 'static/turbulent', 'name'/'inverse''total'relativeUse relative, rather than absolute frequencies withTrue. You can also pass in Series, DataFrame or buzz objects to calculate relative frequencies against the passed in data.Falseremove_above_pSorting by increase/decrease/static/turbulent calculates the slope of the frequencies across each subcorpus, and p-values where the null hypothesis is no slope. If you pass in a float, entries with p-values above this float are dropped from the results. Passing inTruewill use0.05.Falsekeep_statsIf True, keep generated statistics related to the trajectory calculationFalsepreserve_caseKeep the original case forshow(column) valuesFalsemultiindex_columnsWhenshowis a list with multiple features, rather than joiningshowwith slashes, build a multiindexFalseThis creates aTableobject, which is also based on DataFrame. You can use its.view()method to quickly explore results. Pressing enter on a given frequency will bring up a concordance of instances of this entry.PlottingYou can also usebuzzto create high-quality visualisations of frequency data. This relies completely onpandas' plotting method. Aplotmethod more tailored to language datasets is still in development.tab.plot(...)ContributingIf you find bugs, feel free to create an issue. The project is open-source, so pull requests are also welcome. Code style isblack, and versioning is handled bybump2version.