I first made a topic on SO to ask *why* my script is so slow. I then tried to figure out the suggestions to improve the performance of the script. Unfortunately, I am incredibly new to Python but I have some background in PHP and JS. In this post I'll first explain *what the script does* and then I'll ask you to improve the existing code. **Example data and script can be downloaded below.**


I am working on a project which crunches plain text files (.lst).

The name of the file names (`fileName`) are important because I'll extract `node` (e.g. *abessijn*) and `component` (e.g. WR-P-E-A) from them into a dataframe. Examples:

    abessijn.WR-P-E-A.lst
    A-bom.WR-P-E-A.lst
    acroniem.WR-P-E-C.lst
    acroniem.WR-P-E-G.lst
    adapter.WR-P-E-A.lst
    adapter.WR-P-E-C.lst
    adapter.WR-P-E-G.lst

Each file consists of one or more line. Each line consists of a sentence (inside `<sentence>` tags). Example (abessijn.WR-P-E-A.lst)

    /home/nobackup/SONAR/COMPACT/WR-P-E-A/WR-P-E-A0000364.data.ids.xml:  <sentence>Vooral mijn abessijn ruikt heerlijk kruidig .. : ) )</sentence>
    /home/nobackup/SONAR/COMPACT/WR-P-E-A/WR-P-E-A0000364.data.ids.xml:  <sentence>Mijn abessijn denkt daar heel anders over .. : ) ) Maar mijn kinderen richt ik ook niet af , zit niet in mijn bloed .</sentence>

From each line I extract the sentence, do some small modifications to it, and call it `sentence`. Up next is an element called `leftContext`, which takes the first part of the split between `node` (e.g. *abessijn*) and the sentence it came from. Finally, from `leftContext` I get precedingWord, which is the word preceding `node` in `sentence`, or the right most word in `leftContext` (with some limitations such as the option of a compound formed with a hyphen). Example:

    ID | filename             | node | component | precedingWord      | leftContext                               |  sentence
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    1    adapter.WR-P-P-F.lst  adapter  WR-P-P-F   aanpassingseenheid  Een aanpassingseenheid (                      Een aanpassingseenheid ( adapter ) , 
    2    adapter.WR-P-P-F.lst  adapter  WR-P-P-F   toestel             Het toestel (                                 Het toestel ( adapter ) draagt zorg voor de overbrenging van gegevens
    3    adapter.WR-P-P-F.lst  adapter  WR-P-P-F   de                  de aansluiting tussen de sensor en de         de aansluiting tussen de sensor en de adapter , 
    4    airbag.WS-U-E-A.lst   airbag   WS-U-E-A   den                 ja voor den                                   ja voor den airbag op te pompen eh :p
    5    airbag.WS-U-E-A.lst   airbag   WS-U-E-A   ne                  Dobby , als ze valt heeft ze dan wel al ne    Dobby , als ze valt heeft ze dan wel al ne airbag hee



That dataframe is exported as dataset.csv.

After that, the intention of my project comes at hand: I create a frequency table that takes `node` and `precedingWord` into account. From a variable I define `neuter` and `non_neuter`, e.g (in Python)

    neuter = ["het", "Het"]
    non_neuter = ["de","De"]

and a rest category `unspecified`. When `precedingWord` is an item from the list, assign it to the variable. Example of a frequency table output:

    node    |   neuter   | nonNeuter   | unspecified
    -------------------------------------------------
    A-bom       0          4             2
    acroniem    3          0             2
    act         3          2             1

The frequency list is exported as frequencies.csv.


Finally I came up with the following script ([paste][2]) to run.

    import os, pandas as pd, numpy as np, regex as re
    
    from glob import glob
    from datetime import datetime
    from html import unescape
    
    start_time = datetime.now()
    
    # Create empty dataframe with correct column names
    columnNames = ["fileName", "component", "precedingWord", "node", "leftContext", "sentence" ]
    df = pd.DataFrame(data=np.zeros((0,len(columnNames))), columns=columnNames)
    
    # Create correct path where to fetch files
    subdir = "rawdata"
    path = os.path.abspath(os.path.join(os.getcwd(), os.pardir, subdir))
    
    # "Cache" regex
    # See http://stackoverflow.com/q/452104/1150683
    p_filename = re.compile(r"[./\\]")
    
    p_sentence = re.compile(r"<sentence>(.*?)</sentence>")
    p_typography = re.compile(r" (?:(?=[.,:;?!) ])|(?<=\( ))")
    p_non_graph = re.compile(r"[^\x21-\x7E\s]")
    p_quote = re.compile(r"\"")
    p_ellipsis = re.compile(r"\.{3}(?=[^ ])")
    
    p_last_word = re.compile(r"^.*\b(?<!-)(\w+(?:-\w+)*)[^\w]*$", re.U)
    
    # Loop files in folder
    for file in glob(path+"\\*.lst"):
        with open(file, encoding="utf-8") as f:
            [n, c] = p_filename.split(file.lower())[-3:-1]
            fn = ".".join([n, c])
            for line in f:
                s = p_sentence.search(unescape(line)).group(1)
                s = s.lower()
                s = p_typography.sub("", s)
                s = p_non_graph.sub("", s)
                s = p_quote.sub("'", s)
                s = p_ellipsis.sub("... ", s)
    
                if n in re.split(r"[ :?.,]", s):
                    lc = re.split(r"(^| )" + n + "( |[!\",.:;?})\]])", s)[0]
        
                    pw = p_last_word.sub("\\1", lc)
        
                    df = df.append([dict(fileName=fn, component=c, 
                                       precedingWord=pw, node=n, 
                                       leftContext=lc, sentence=s)])
                continue
    
    # Reset indices
    df.reset_index(drop=True, inplace=True)
    
    # Export dataset
    df.to_csv("dataset/py-dataset.csv", sep="\t", encoding="utf-8")
    
    # Let's make a frequency list
    # Create new dataframe
    
    # Define neuter and non_neuter
    neuter = ["het"]
    non_neuter = ["de"]
    
    # Create crosstab
    df.loc[df.precedingWord.isin(neuter), "gender"] = "neuter"
    df.loc[df.precedingWord.isin(non_neuter), "gender"] = "non_neuter"
    df.loc[df.precedingWord.isin(neuter + non_neuter)==0, "gender"] = "rest"
    
    freqDf = pd.crosstab(df.node, df.gender)
    
    freqDf.to_csv("dataset/py-frequencies.csv", sep="\t", encoding="utf-8")
    
    # How long has the script been running?
    time_difference = datetime.now() - start_time
    print("Time difference of", time_difference)


I am running on Windows 10 64 bit with a quad-core processor and 8 GB Ram. Python runs on version *3.4.3* (Anaconda) and is executed in Spyder. Note that I'm running Python in 32 bit because I'd like to use the [nltk module][3] in the future and they discourage users to use  64 bit.

Seeing that the goal of Python was to make everything go smoothly, I was confused. **The goal is to beat a similar R script in execution speed.** I read Python was fast, so where did I go wrong? What is the problem? Is Python slower in reading files and lines, or in doing regexes? Or is R simply better equipped to dealing with dataframes and can't it be beaten by pandas? *Or* is my code simply badly optimised and should Python indeed be the victor?

Everyone who is willing to give the script a try can download the test data I used [**here**][4]. Please give me a heads-up when you downloaded the files.

Please, if you take the time to re-write my efforts, do make comments in the script explaining what code does what. I am very new to this language and I like to learn more.

  [1]: http://pastebin.com/gtCrAdMq
  [2]: http://pastebin.com/rPFJKuBV
  [3]: http://www.nltk.org/
  [4]: http://bramvanroy.be/files/testdata.zip