<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>GSoC | Lenka&#39;s blog</title>
    <link>https://lenkastatham.com/tags/gsoc.html</link>
      <atom:link href="https://lenkastatham.com/tags/gsoc/index.xml" rel="self" type="application/rss+xml" />
    <description>GSoC</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-gb</language><lastBuildDate>Sat, 29 May 2021 00:00:00 +0000</lastBuildDate>
    <image>
      <url>https://lenkastatham.com/img/icon-192.png</url>
      <title>GSoC</title>
      <link>https://lenkastatham.com/tags/gsoc.html</link>
    </image>
    
    <item>
      <title>Google Summer of Code 2021</title>
      <link>https://lenkastatham.com/post/gsoc.html</link>
      <pubDate>Sat, 29 May 2021 00:00:00 +0000</pubDate>
      <guid>https://lenkastatham.com/post/gsoc.html</guid>
      <description>&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;
&lt;h3 id=&#34;introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Welcome!&lt;/p&gt;
&lt;p&gt;My name is Lenka, I am currently working on my PhD in &lt;a href=&#34;http://www.bristol.ac.uk/cmm/aqm/&#34;&gt;Advanced Quantitative Geography at Bristol University &lt;/a&gt;, and over the summer 2021 I worked alongside &lt;a href=&#34;http://ljwolf.org/&#34;&gt;Dr Levi Wolf&lt;/a&gt; and &lt;a href=&#34;https://geog.umd.edu/facultyprofile/oshan/taylor&#34;&gt;Dr Taylor Oshan&lt;/a&gt; on implementation of &lt;a href=&#34;https://journals.sagepub.com/doi/10.1177/0308518X8301500103&#34;&gt;Competing Destination models&lt;/a&gt; for &lt;a href=&#34;https://github.com/pysal/spint&#34;&gt;SpInt python package&lt;/a&gt;, as part of Google Summer of Code 2021.&lt;/p&gt;
&lt;h4 id=&#34;links--references&#34;&gt;Links &amp;amp; references&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you are future GSoC participant and you are interested in seeing my &lt;strong&gt;proposal&lt;/strong&gt;, it is still living on my &lt;a href=&#34;https://docs.google.com/document/d/1VP77-qC2dXMa1_aYVM_R-q7a9DZS5WXHbw0QRN3v2A4/edit?usp=sharing&#34;&gt;google drive&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To see all the work and commits, see this &lt;a href=&#34;https://github.com/pysal/spint/pull/32&#34;&gt;&lt;strong&gt;Github pull request&lt;/strong&gt;&lt;/a&gt;. Or find my working repository &lt;a href=&#34;https://github.com/lenkahas/spint&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you want to know how the work progressed step by step, keep reading this blog. Each entry has title, so you can jump to the most recent entry in the table of contents.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you are interested what other projects look like, have a look at blogs of &lt;a href=&#34;https://noahbouchier.github.io/blog/gsoc/gsoc-intro/&#34;&gt;Noah&lt;/a&gt; and &lt;a href=&#34;https://gegen07.github.io/#/blog&#34;&gt;Germano&lt;/a&gt; who also completed their projects for Pysal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;state-of-the-project&#34;&gt;State of the project&lt;/h3&gt;
&lt;p&gt;I am happy to say that everything stated in proposal has been completed. We have managed to create accessibility function, working test and notebook with an example. We also managed to optimize the function for big datasets and significantly speed up the calculation.
Link for the pull request including the whole code is above. The blog documenting tho whole progress is just below, so keep reading if you are interested in that.&lt;/p&gt;
&lt;h3 id=&#34;summary--final-thoughts&#34;&gt;Summary &amp;amp; Final thoughts&lt;/h3&gt;
&lt;p&gt;We had a lot of discussion about the form of the accessibility. This is a duifficult one as there is many forms and ways it can be calculated, and incorporating them all into this the module would be close to impossible. By defining the function as generally as we did in this project allows the users to interchange wight variables, use different masses and even calculate accessibility for bipartite or tripirtite interaction systems. We hope this will be usefull to many researchers and python users who are looking for Competiong Destination replication.&lt;/p&gt;
&lt;p&gt;I loved to be part of GSoC project, as well as the Pysal community. This experience gave me a lot of insight into python developement and especially into the community proccess of developement. This is something that I would not be able to experience otherwise.&lt;/p&gt;
&lt;h2 id=&#34;blog&#34;&gt;Blog&lt;/h2&gt;
&lt;h3 id=&#34;1-community-bonding-week-24th-may-till-1th-june&#34;&gt;1. Community Bonding, week 24th May till 1th June&lt;/h3&gt;
&lt;h4 id=&#34;meeting-the-team&#34;&gt;Meeting the team&lt;/h4&gt;
&lt;p&gt;On the first meeting, I have been able to connect with my mentors Levi, who is also my PhD supervisor, and Taylor, who is an author of the SpInt package. It has been such a pleasure to connect with Taylor who lives roughly 8.5 thousand kilometres (yes I work in km&#39;s and I&#39;m not aiming to change that, so deal with it) away from Bristol where me and Levi are based. We chat about their own experiences with GSoC, as they both have been a participants and now mentors, I was interested in their perspective.&lt;br&gt;
We have also briefly chat about how the SpInt package is structured, so I get better idea of what will the task entail, and what the first steps are.&lt;/p&gt;
&lt;h4 id=&#34;meeting-the-pysal-community-and-other-students-with-their-project&#34;&gt;Meeting the Pysal community and other students with their project.&lt;/h4&gt;
&lt;p&gt;The pysal community has a meeting once a month, where they discuss current problems, updates and future improvements.
One of the main things discussed on the meeting was the problem of fragmented graph packages and their inability to cater for translation of graphs from other packages such as street network graphs from osmnx. This is very relevant to my work as I have bumped into this issue in March this year. I was trying to speed up the computation of distances along the road for bunch of points and I found that &lt;a href=&#34;https://github.com/gboeing/osmnx&#34;&gt;osmnx&lt;/a&gt; has the best function for loading street network based on bounding boxes, but &lt;a href=&#34;https://github.com/UDST/pandana&#34;&gt;pandana&lt;/a&gt; has the fastest computation of shortest paths along the graph (the package is build on C++). To combine these two methods, we just need to translate the graph from osmnx graph to pandana graph. Check out &lt;a href=&#34;https://lenkahas.com/post/pandana.html#&#34;&gt;this post&lt;/a&gt; to see how this is done and how you can calculate shortest paths using pandana.&lt;/p&gt;
&lt;p&gt;It&#39;s been good to see how the community takes care of the open-source software, as this part is well hidden from an average user.&lt;/p&gt;
&lt;h3 id=&#34;2-first-coding-period-week-1&#34;&gt;2. First Coding period. WEEK 1&lt;/h3&gt;
&lt;h4 id=&#34;project-introduction&#34;&gt;Project introduction&lt;/h4&gt;
&lt;p&gt;For the general introduction into the project please read the &lt;a href=&#34;https://docs.google.com/document/d/1VP77-qC2dXMa1_aYVM_R-q7a9DZS5WXHbw0QRN3v2A4/edit&#34;&gt;proposal&lt;/a&gt;, which provides an overview for the problem.&lt;/p&gt;
&lt;p&gt;There is one main deliverable for this project and that is a function that calculate Accessibility term for competing destination models.
This can be broken down into X steps.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create initial accessibility function&lt;/li&gt;
&lt;li&gt;Create functional test to validate the function and its future versions&lt;/li&gt;
&lt;li&gt;Research how the function could be speeded up&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;what-is-the-accessibility-function&#34;&gt;What is the accessibility function&lt;/h4&gt;
&lt;p&gt;According the initial paper from &lt;a href=&#34;https://risweb.st-andrews.ac.uk/portal/en/researchoutput/a-new-set-of-spatial-interaction-models-the-theory-of-competing-destinations(8c3f7c99-5efc-4774-9d91-17e0482cecfd)/export.html&#34;&gt;Fotheringham (1983)&lt;/a&gt; the accessibility $A_{ij}$ represents the accessibility of destination $j$ to all other destinations available to origin $i$ as perceived by the residents of origin $i$ and is defined as&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./gsoc/acc1.png&#34; alt=&#34;png&#34;&gt;&lt;/p&gt;
&lt;p&gt;This can be little confusing, so let&#39;s break it down.&lt;/p&gt;
&lt;p&gt;Imagine a migration between 5 following English cities, where people from Liverpool migrate to South Hampton, Bristol and London, and people from London migrate to Brighton.
If we want to calculate the accessibility term for the flow Liverpool to Bristol, we consider following; The accessibility of $\text{flow}_{Li,B}$ is the sum of the connections between the destination (Bristol) to all other destinations. This connection or accessibility between Bristol and other destinations can be then calculated as the distance between Bristol and destination times the mass of the destination.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./gsoc/Graph_acc.png&#34; alt=&#34;png&#34;&gt;&lt;/p&gt;
&lt;p&gt;In other words;&lt;/p&gt;
&lt;p&gt;$$  \text{Accessibility for the flow between Liverpool and Bristol} = \sum \text{population SH * distance1, population Lo * distance2} $$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: We don&#39;t necessarily know if this is meant as an accessibility of all possible destinations in a system, or only those that actually exist for given origin.&lt;/p&gt;
&lt;p&gt;I could design 2 functions&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. Accessibility of flow taking all potential destinations in the system 
2. Accessibility of flow taking only existing destinations
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In those two functions, we would assume that the input is just one Data frame: Data Frame of all possible flows in system with distances between them, masses on destinations and the flow volumes between existing flows (0 where no flow exists).
First I draw the second function which accounts only for those connections that exists in flow network.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./gsoc/table2.png&#34; alt=&#34;png&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;initial-accessibility-function&#34;&gt;Initial accessibility function&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;# coding=utf-8
# 3. AFED = Accessibility of flow taking existing destinations

def AFED(flow_df, row_index): # AFAPF
    
    # rename teh columns so we can call them 
    flow_df = flow_df.rename(columns = {flow_df.columns[0]:&#39;origin_ID&#39;, 
                                            flow_df.columns[1]:&#39;dest_ID&#39;, 
                                            flow_df.columns[2]:&#39;dist&#39;, 
                                            flow_df.columns[3]:&#39;weight&#39;, 
                                            flow_df.columns[4]:&#39;dest_mass&#39;})
    # define O and D for each row the variables
    D = flow_df[&#39;dest_ID&#39;][row_index]
    O = flow_df[&#39;origin_ID&#39;][row_index]
    
    # get the list of possible destinations
    all_dest = (flow_df.query(&#39;origin_ID == @O&#39;)
                .query(&#39;weight &amp;gt; 0&#39;)
                [&#39;dest_ID&#39;]
                .unique()
               )    
    
    # Create all destination flows 
    x1 = pd.DataFrame({&#39;D&#39;: np.array([D]*len(all_dest), dtype=object), 
                       &#39;dests&#39;:all_dest}).merge(flow_df, how=&#39;left&#39;, left_on=[&#39;D&#39;,&#39;dests&#39;], right_on=[&#39;origin_ID&#39;,&#39;dest_ID&#39;])
    
    # merge with the distances and masses 
    
    # Delete the flow to origin
    x1 = x1[~x1.dests.isin(list(O))]    

    # calculate the accessibility
    A = (x1[&#39;dist&#39;]*x1[&#39;dest_mass&#39;]).sum()

    return A
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;further-notes-on-accessibility-function&#34;&gt;Further notes on accessibility function&lt;/h4&gt;
&lt;p&gt;I have described accessibility function defined by &lt;a href=&#34;https://risweb.st-andrews.ac.uk/portal/en/researchoutput/a-new-set-of-spatial-interaction-models-the-theory-of-competing-destinations(8c3f7c99-5efc-4774-9d91-17e0482cecfd)/export.html&#34;&gt;Fotheringham (1983)&lt;/a&gt; as very generic function of distances and masses, however, more specific definition with different weighting could be considered for specific situations. For example, some could opt to substitute distance for some more specific measure of separation, such as travel time or journey cost. Similarly, there is many things that could be considered as a mass on the destination.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: it would be good to discuss the potential of the function to cater for alternative definitions of accessibility&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;3-first-coding-period-week-2-and-3&#34;&gt;3. First Coding period. WEEK 2 and 3&lt;/h3&gt;
&lt;h4 id=&#34;extending-the-accessibility-calculation-opt-for-all-possible-connections-or-just-existing-connections&#34;&gt;Extending the accessibility calculation, opt for all possible connections or just existing connections&lt;/h4&gt;
&lt;p&gt;As I said last week (few lines above), you can consider either all potential destinations or the existing ones.&lt;/p&gt;
&lt;p&gt;Let me clarify this a bit. Estimating accessibility in a very generic way that we do here, we do in 3 steps.&lt;/p&gt;
&lt;p&gt;For each unique combination of origin A and destination B;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;find all the potential or all existing destinations to origin A&lt;/li&gt;
&lt;li&gt;find the distances between all those destinations and destination B&lt;/li&gt;
&lt;li&gt;compute (distance*destination mass) for all those connections from B to other destinations&lt;/li&gt;
&lt;li&gt;deduct the (distance*destination mass) which belongs to destination B and origin A&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In point 1, all potential destinations are those that exist in a Spatial Interaction system. Even though there is no flow between our origin A and destination X, it is &lt;em&gt;available&lt;/em&gt; for people from Origin A.&lt;/p&gt;
&lt;p&gt;However, if there is no flow between origin A and destination X in a system, then we could assume that people from that origin just do not consider that destination desirable. Thus, as competing destinations we could consider these, that receive at least some flow from origin A (flow volume &amp;gt; 0).&lt;/p&gt;
&lt;p&gt;Have a look at this example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./gsoc/flow_selection.jpg&#34; alt=&#34;jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;In the updated function, I include an if statement which either constructs the competing destinations based on all potential flows or just the existing ones. This is controllable by the argument &lt;em&gt;all_destination = False&lt;/em&gt;, where &lt;strong&gt;False&lt;/strong&gt; (include only the existing flows) is the default option, while &lt;strong&gt;True&lt;/strong&gt; will include all the potential destinations regardless of the flow volume.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;# coding=utf-8
# 3. FlowAccessibility = Accessibility of flow taking existing destinations

def AFED(flow_df, row_index, all_destinations=False): 
    
    # rename teh columns so we can call them 
    flow_df = flow_df.rename(columns = {flow_df.columns[0]:&#39;origin_ID&#39;, 
                                            flow_df.columns[1]:&#39;dest_ID&#39;, 
                                            flow_df.columns[2]:&#39;dist&#39;, 
                                            flow_df.columns[3]:&#39;weight&#39;, 
                                            flow_df.columns[4]:&#39;dest_mass&#39;})
    # define O and D for each row the variables
    D = flow_df[&#39;dest_ID&#39;][row_index]
    O = flow_df[&#39;origin_ID&#39;][row_index]
    
    
    # get the list of possible destinations 
    if all_destinations:
         all_dest = (flow_df.query(&#39;origin_ID == @O&#39;)
                [&#39;dest_ID&#39;]
                .unique()
               )
    else:
        all_dest = (flow_df.query(&#39;origin_ID == @O&#39;)
                .query(&#39;weight &amp;gt; 0&#39;)
                [&#39;dest_ID&#39;]
                .unique()
               )

        
    # Create all destination flows 
    x1 = pd.DataFrame({&#39;D&#39;: np.array([D]*len(all_dest), dtype=object), 
                       &#39;dests&#39;:all_dest}).merge(flow_df, how=&#39;left&#39;, left_on=[&#39;D&#39;,&#39;dests&#39;], right_on=[&#39;origin_ID&#39;,&#39;dest_ID&#39;])
    
    # merge with the distances and masses 
    
    # Delete the flow to origin
    x1 = x1[~x1.dests.isin(list(O))]    

    # calculate the accessibility
    A = (x1[&#39;dist&#39;]*x1[&#39;dest_mass&#39;]).sum()

    return A
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;application-of-accessibility-function-for-whole-dataset&#34;&gt;Application of Accessibility function for whole dataset&lt;/h4&gt;
&lt;p&gt;The function defined above calculates accessibility for a one row of data, for one connection in the interaction system.
In next step, I need to do this for all rows in the dataset. This is probably not the most efficient way, but we need to start somewhere.&lt;/p&gt;
&lt;p&gt;Below, I define another function that takes the accessibility computation, loops it over the full dataset supplied, and tells you how long it took.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def Accessibility(flow_df, function, all_destinations=False):
    start = timer()

    A_ij = []

    for idx in flow_df.index:
       
            if all_destinations: 
                A = function(flow_df=flow_df, row_index=idx, all_destinations=True)
            else:
                A = function(flow_df=flow_df, row_index=idx, all_destinations=False)
            A_ij.append(A)
                
    A_ij = pd.Series(A_ij)
    end = timer()

    print(&#39;time elapsed: &#39; + str(end - start))
    return A_ij
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;contributing-to-github&#34;&gt;Contributing to GitHub&lt;/h4&gt;
&lt;p&gt;So, now is the time to get these online into a repo!&lt;/p&gt;
&lt;p&gt;If you are not familiar with a GitHub yet, here are some useful links&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=w3jLJU7DT5E&#34;&gt;Here is a video&lt;/a&gt; with the most effective explanation of what is GitHub.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=_NrSWLQsDL4&amp;amp;t=491s&#34;&gt;This video&lt;/a&gt; explains and demonstrates forking, branching merging, pull requests and even more. I like this video and the presenter because he is very humanly and slightly quirky.&lt;/li&gt;
&lt;li&gt;On the same channel, you can also find &lt;a href=&#34;https://www.youtube.com/watch?v=oPpnCh7InLY&#34;&gt;branching video&lt;/a&gt; and [Intro to GitHub video](](&lt;a href=&#34;https://www.youtube.com/watch?v=_NrSWLQsDL4&amp;amp;t=491s)&#34;&gt;https://www.youtube.com/watch?v=_NrSWLQsDL4&amp;amp;t=491s)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=HVsySz-h9r4&#34;&gt;This video&lt;/a&gt; provides a tutorial for the command line fundamentals. You can find more specific step-by-step guide &lt;a href=&#34;https://git-scm.com/book/en/v2/Getting-Started-The-Command-Line&#34;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Lastly, here is a cheat-sheet that I created for myself. I am one of these people who do not hold in memory things that I can easily find online, however, that can be sometimes a bit lengthy. So, cheat-sheets are the best way I can quickly access things that I already did, but don&#39;t hold them in memory.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./gsoc/GIT_cheat_sheet.png&#34; alt=&#34;png&#34;&gt;&lt;/p&gt;
&lt;p&gt;To contribute to a package, I did this;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;fork the pysal/spint package = create copied-repository on my own GitHub&lt;/li&gt;
&lt;li&gt;commit changes, new codes and fixes into this repo over the period of this project&lt;/li&gt;
&lt;li&gt;once this project is getting to the end, and everybody is happy with the contribution, I will create pull request to send the code from my repo to the official pysal repo.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;4-first-coding-period-week-4-and-5&#34;&gt;4. First Coding period. WEEK 4 and 5&lt;/h3&gt;
&lt;h4 id=&#34;testing-and-validation&#34;&gt;Testing and validation&lt;/h4&gt;
&lt;p&gt;the next step is to create an efficient test, which will be automatically checking if the version of the function in the package is correct. To do this, I need to;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;create a simple dummy dataset for which I manually calculate the desired accessibility values&lt;/li&gt;
&lt;li&gt;create test_accessibility.py script that will use the function from the package to calculate accessibility for dummy data and check if it equals to manually calculated values.&lt;/li&gt;
&lt;li&gt;make sure this test runs correctly within the framework&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;create-dummy-dataset&#34;&gt;Create dummy dataset&lt;/h4&gt;
&lt;p&gt;The simplest way to test if function works is to see if its output matches the expected output. Nothing scary, nothing revolutionary. Simple yet effective.&lt;/p&gt;
&lt;p&gt;Below you can see simple data set that I use for testing. It only has 5 points in space, but it has all possible types of connections and has realistic masses and distances.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./gsoc/dummy.png&#34; alt=&#34;png&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;create-test&#34;&gt;Create test&lt;/h4&gt;
&lt;p&gt;I was not familiar with testing in python until now, so I needed to do some studying for this.
I found these two links very useful;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://realpython.com/python-testing/&#34;&gt;Getting Started With Testing in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://realpython.com/pytest-python-testing/&#34;&gt;Effective Python Testing With Pytest&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are two modules used for testing in python; pytest and unittest. Pytest is the newer one and is super easy to use. Check out &lt;a href=&#34;https://www.youtube.com/watch?v=byaxg00Gf9I&#34;&gt;this quick tutorial&lt;/a&gt;. While unittest is the more established module, that is already used within the pysal/spint.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;&amp;quot;&amp;quot;&amp;quot;
Tests for Accessibility function.

The correctness of the function is veryfied by matching the output to manually calculated values, using very simple dummy dataset.

&amp;quot;&amp;quot;&amp;quot;

__author__ = &#39;Lenka Hasova haska.lenka@gmail.com&#39;

import unittest
import numpy as np
from generate_dummy import generate_dummy_flows # this is a function that generates dummy flow
from ..flow_accessibility import Accessibility # This is the function that applies accessibility to whole dataset
from ..flow_accessibility import AFED # Accessibility function


class AccessibilityTest(unittest.TestCase):
    
    def test_accessibility(self):
        flow = generate_dummy_flows()
        flow = flow.loc[:,[&#39;origin_ID&#39;, &#39;destination_ID&#39;,&#39;distances&#39;, &#39;volume_in_unipartite&#39;,&#39;dest_masses&#39;,&#39;results_all=False&#39;]]
        flow[&#39;acc_uni&#39;] = Accessibility(flow_df = flow, all_destinations=False, function = AFED)
        
        self.assertEqual(flow[&#39;results_all=False&#39;].all(), flow[&#39;acc_uni&#39;].all())

if __name__ == &#39;__main__&#39;:
    unittest.main()
    

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the test on your local machine, just open your anaconda prompt, navigate to directory where all the tests are and run following&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;$ python -m unittest test_accessibility.py&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or simply just push the code online and login to Travis.Cl. Travis runs all the test every time changes are made in your repo. Don&#39;t forget to signup to Travis and connect it to your GitHub.
So cool!&lt;/p&gt;
&lt;h3 id=&#34;5-whats-next&#34;&gt;5. What&#39;s next?&lt;/h3&gt;
&lt;p&gt;With the test that works, most of the important work on this project is basically ready to be submitted. However, because the function compromises 2 for loops, it is more likely to be very slow when you apply it on the big data.
In real word research, you don&#39;t just have 5 points in space that creates $5*5=25$ flows. Often you have tens, hundreds and even thousands of points. For example, if you would have to compute accessibility for all the flows between all UK train stations (there is 2,563 of them) you would have 6,568,969 flows.&lt;/p&gt;
&lt;p&gt;The rest of the GSoC project I will dedicate to trying to find out how fast is the current function, what is the best way to speed it up and how that can be implemented in practice.&lt;/p&gt;
&lt;h3 id=&#34;6-second-coding-period-making-the-code-faster&#34;&gt;6. Second coding period, making the code faster&lt;/h3&gt;
&lt;p&gt;The function I wrote initially can be quite slow. We can already tell that from the code, where we use &lt;strong&gt;for loop&lt;/strong&gt; to calculate accessibility for each row of your dataset.
for loops are not very efficient way to compute things, unless you have very few jobs.&lt;/p&gt;
&lt;p&gt;There are few ways the speed up your code. Here are the two most appropriate options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.paperspace.com/numpy-optimization-vectorization-and-broadcasting/&#34;&gt;Vectorization or Broadcasting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://numba.pydata.org/numba-doc/latest/user/5minguide.html&#34;&gt;Use Numba&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If I would really need the loops, Numba is a really good way to speed it up by allowing for parralel processing. But sometimes the loops are not that necessary and can be rewriten more efficiently using arrays and matrices.
So lets try to do that!&lt;/p&gt;
&lt;p&gt;The code bellows shows you 3 functions and compares their speed on real data (set of points located within Bristol).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;orginal function from above (AFED)&lt;/li&gt;
&lt;li&gt;function that loops over set of arrays (AFED_MAT)&lt;/li&gt;
&lt;li&gt;function that multiplies 3-d arrays (no loops) (AFED_MAT_2)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;import numpy as np
import pandas as pd
import geopandas as gpd
import itertools
import libpysal
from timeit import default_timer as timer
import matplotlib.pyplot as plt

from geopy.distance import geodesic, great_circle
from pyproj import CRS
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;define-the-functions&#34;&gt;Define the functions&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def AFED(flow_df, row_index, all_destinations=False): 
    
    # rename teh columns so we can call them 
    flow_df = flow_df.rename(columns = {flow_df.columns[0]:&#39;origin_ID&#39;, 
                                            flow_df.columns[1]:&#39;dest_ID&#39;, 
                                            flow_df.columns[2]:&#39;dist&#39;, 
                                            flow_df.columns[3]:&#39;weight&#39;, 
                                            flow_df.columns[4]:&#39;dest_mass&#39;})

    
    flow_df[&#39;dist&#39;] = flow_df[&#39;dist&#39;].astype(int)
    flow_df[&#39;weight&#39;] = flow_df[&#39;weight&#39;].astype(int)
    flow_df[&#39;dest_mass&#39;] = flow_df[&#39;dest_mass&#39;].astype(int)
    
        # define O and D for each row the variables
    D = flow_df[&#39;dest_ID&#39;][row_index]
    O = flow_df[&#39;origin_ID&#39;][row_index]
    
    
    # get the list of possible destinations 
    if all_destinations:
         all_dest = (flow_df.query(&#39;origin_ID == @O&#39;)
                [&#39;dest_ID&#39;]
                .unique()
               )
    else:
        all_dest = (flow_df.query(&#39;origin_ID == @O&#39;)
                .query(&#39;weight &amp;gt; 0&#39;)
                [&#39;dest_ID&#39;]
                .unique()
               )

        
    # Create all destination flows 
    x1 = pd.DataFrame({&#39;D&#39;: np.array([D]*len(all_dest), dtype=object
                                    ), &#39;dests&#39;:all_dest}
                     ).merge(flow_df, how=&#39;left&#39;, left_on=[&#39;D&#39;,&#39;dests&#39;], right_on=[&#39;origin_ID&#39;,&#39;dest_ID&#39;])
    
    # merge with the distances and masses 
    
    # Delete the flow to origin
    x1 = x1[~x1.dests.isin([O])]    

    # calculate the accessibility
    A = (x1[&#39;dist&#39;]*x1[&#39;dest_mass&#39;]).sum()

    return A

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;import numpy as np
import pandas as pd
from timeit import default_timer as timer
import itertools


def AFED_MAT(flow_df, all_destinations=False):
    
     # rename teh columns so we can call them 
    flow_df = flow_df.rename(columns = {flow_df.columns[0]:&#39;origin_ID&#39;, 
                                            flow_df.columns[1]:&#39;dest_ID&#39;, 
                                            flow_df.columns[2]:&#39;dist&#39;, 
                                            flow_df.columns[3]:&#39;weight&#39;, 
                                            flow_df.columns[4]:&#39;dest_mass&#39;})
    
    flow_df[&#39;dist&#39;] = flow_df[&#39;dist&#39;].astype(int)
    flow_df[&#39;weight&#39;] = flow_df[&#39;weight&#39;].astype(int)
    flow_df[&#39;dest_mass&#39;] = flow_df[&#39;dest_mass&#39;].astype(int)
    
    # create binary for weight
    flow_df[&#39;v_bin&#39;] = 1
    flow_df[&#39;v_bin&#39;][(flow_df[&#39;weight&#39;].isna() | flow_df[&#39;weight&#39;] == 0 ) ] = 0
    
    # define base matrices
    distance = flow_df.pivot_table(values=&#39;dist&#39;, index=&#39;dest_ID&#39;, columns=&amp;quot;origin_ID&amp;quot;)
    mass = flow_df.pivot_table(values=&#39;dest_mass&#39;, columns=&#39;dest_ID&#39;, index=&amp;quot;origin_ID&amp;quot;)
    exists = flow_df.pivot_table(values=&#39;v_bin&#39;, index=&#39;dest_ID&#39;, columns=&amp;quot;origin_ID&amp;quot;)
    
    # lists of O-D
    list_c = list(itertools.product(exists.columns,exists.index))
    df = pd.DataFrame(list_c).rename(columns = {0:&#39;origin_ID&#39;, 1:&#39;dest_ID&#39;})
    
    # Start function
    array = []
    start = timer()
    for i,j in list_c:
        
        if all_destinations:
            f = exists.loc[i,:]
            f[0] =  1
            f[f.index.isin([i])] = 0
        else:
            f = exists.loc[i,:]
            f[f.index.isin([i])] = 0
            
        empty = pd.DataFrame(np.zeros((len(exists.index),len(exists.columns))), index = exists.index, columns = exists.columns)
        empty.loc[j,:] = f
        A_ij = empty *  (distance * mass)
        A_ij = A_ij.sum().sum()
        array.append(A_ij)
    
    # Get the result back to original dataframe
    df[&#39;A_ij&#39;] = array
    flow_df = flow_df.merge(df, how = &#39;left&#39;, on = [&#39;origin_ID&#39;,&#39;dest_ID&#39;])

    #how long did this took?
    end = timer()
    print(&#39;time elapsed: &#39; + str(end - start))
    return(flow_df)


&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def AFED_VECTOR(flow_df, all_destinations=False):
    start = timer()
     # rename teh columns so we can call them 
    flow_df = flow_df.rename(columns = {flow_df.columns[0]:&#39;origin_ID&#39;, 
                                            flow_df.columns[1]:&#39;dest_ID&#39;, 
                                            flow_df.columns[2]:&#39;dist&#39;, 
                                            flow_df.columns[3]:&#39;weight&#39;, 
                                            flow_df.columns[4]:&#39;dest_mass&#39;})
    
    flow_df[&#39;dist&#39;] = flow_df[&#39;dist&#39;].astype(int)
    flow_df[&#39;weight&#39;] = flow_df[&#39;weight&#39;].astype(int)
    flow_df[&#39;dest_mass&#39;] = flow_df[&#39;dest_mass&#39;].astype(int)
    # create binary for weight
    flow_df[&#39;v_bin&#39;] = 1
    flow_df.loc[flow_df[&#39;weight&#39;].isna(),&#39;v_bin&#39;] = 0
    flow_df.loc[flow_df[&#39;weight&#39;] &amp;lt;= 0,&#39;v_bin&#39;] = 0
    
    # define the base matrices
    distance = np.array(flow_df.pivot_table(values=&#39;dist&#39;, index=&#39;origin_ID&#39;, columns=&amp;quot;dest_ID&amp;quot;))
    mass = np.array(flow_df.pivot_table(values=&#39;dest_mass&#39;, columns=&amp;quot;origin_ID&amp;quot;, index=&#39;dest_ID&#39;))
    exists = np.array(flow_df.pivot_table(values=&#39;v_bin&#39;, index=&#39;dest_ID&#39;, columns=&amp;quot;origin_ID&amp;quot;))
    
    # define the base 3d array
    nrows= len(exists)
    ones = np.ones((nrows,len(flow_df.origin_ID.unique()),len(flow_df.dest_ID.unique())))
    
    # define the identity array
    idn = np.identity(nrows) 
    idn = np.where((idn==1), 0, 1)
    idn = np.concatenate(nrows * [idn.ravel()], axis = 0).reshape(nrows,nrows,nrows).T

    # multiply the distance by mass
    ard = np.array(distance)*np.array(mass)
    
    # combine all into and calculate the output
    
    if all_destinations:
        output = np.array(idn) * (np.array(nrows * [ard]
                                              )
                                     )
        
    else:
        output = (np.concatenate(nrows * [exists], axis = 0
                                ).reshape(nrows,nrows,nrows
                                         ).T 
                 ) * np.array(idn) * (np.array(nrows * [ard]
                                              )
                                     )
    
    # get the sum and covert to series
    g = pd.DataFrame((
        np.sum(output,axis = 1
                           )  ).reshape(1,len(flow_df)
                                                    ).T
                    )
    
    end = timer()
    print(&#39;time elapsed: &#39; + str(end - start))
    return g[0]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;define-the-data-sets-for-testing&#34;&gt;Define the data sets for testing&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;pois = gpd.read_file(&amp;quot;./../points.geojson&amp;quot;)
pois.info()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;class &#39;geopandas.geodataframe.GeoDataFrame&#39;&amp;gt;
RangeIndex: 855 entries, 0 to 854
Data columns (total 4 columns):
 #   Column    Non-Null Count  Dtype   
---  ------    --------------  -----   
 0   ID_code   855 non-null    object  
 1   X         855 non-null    float64 
 2   Y         855 non-null    float64 
 3   geometry  855 non-null    geometry
dtypes: float64(2), geometry(1), object(1)
memory usage: 26.8+ KB
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;# strip
pois_all = pois.iloc[:,[0,3]]

# Add random masses
pois_all[&#39;mass&#39;] = pd.Series( np.random.randint(0,1500, size=len(pois_all))
                            )
# create stripped column of coordinates 
pois_all[&#39;xy&#39;] = pois_all.geometry.apply(lambda x: [x.y, x.x])

# get all unique combinations of all the origins and destinations
flow_all = pd.DataFrame( list( itertools.product( pois_all[&#39;ID_code&#39;].unique(),pois_all[&#39;ID_code&#39;].unique())
                             )
                       ).rename(columns = {0:&#39;origin&#39;,
                                        1:&#39;destination&#39;})

# joining the xy to flows and create distances
distances_all = flow_all.merge(pois_all.loc[:,[&#39;ID_code&#39;,&#39;xy&#39;]], how = &#39;left&#39;, left_on = &#39;origin&#39;, right_on = &#39;ID_code&#39; 
                        ).merge(pois_all.loc[:,[&#39;ID_code&#39;,&#39;xy&#39;]], how = &#39;left&#39;, left_on = &#39;destination&#39;, right_on = &#39;ID_code&#39;
                               )


# calculate distances
distances_all[&#39;great_circle_dist&#39;] = distances_all.apply(lambda x: great_circle(x.xy_x, x.xy_y).km, axis=1)
distances_all = distances_all.loc[:,[&#39;origin&#39;,&#39;destination&#39;,&#39;great_circle_dist&#39;]]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;# list points
list5 = list(pois_all[&#39;ID_code&#39;][0:6])
list10 = list(pois_all[&#39;ID_code&#39;][0:11])
list20 = list(pois_all[&#39;ID_code&#39;][0:21])
list40 = list(pois_all[&#39;ID_code&#39;][0:41])
list80 = list(pois_all[&#39;ID_code&#39;][0:81])
list160 = list(pois_all[&#39;ID_code&#39;][0:161])

# Define different number of points
pois_all_s = pd.DataFrame(pois_all.loc[:,[&#39;ID_code&#39;,&#39;mass&#39;]])
pois5 = pois_all_s[pois_all_s[&#39;ID_code&#39;].isin(list5)]
pois10 = pois_all_s[pois_all_s[&#39;ID_code&#39;].isin(list10)]
pois20 = pois_all_s[pois_all_s[&#39;ID_code&#39;].isin(list20)]
pois40 = pois_all_s[pois_all_s[&#39;ID_code&#39;].isin(list40)]
pois80 = pois_all_s[pois_all_s[&#39;ID_code&#39;].isin(list80)]
pois160 = pois_all_s[pois_all_s[&#39;ID_code&#39;].isin(list160)]

flow5 = flow_all[flow_all[&#39;origin&#39;].isin(list5) &amp;amp; flow_all[&#39;destination&#39;].isin(list5)].reset_index(drop=True)
flow10 = flow_all[flow_all[&#39;origin&#39;].isin(list10) &amp;amp; flow_all[&#39;destination&#39;].isin(list10)].reset_index(drop=True)
flow20 = flow_all[flow_all[&#39;origin&#39;].isin(list20) &amp;amp; flow_all[&#39;destination&#39;].isin(list20)].reset_index(drop=True)
flow40 = flow_all[flow_all[&#39;origin&#39;].isin(list40) &amp;amp; flow_all[&#39;destination&#39;].isin(list40)].reset_index(drop=True)
flow80 = flow_all[flow_all[&#39;origin&#39;].isin(list80) &amp;amp; flow_all[&#39;destination&#39;].isin(list80)].reset_index(drop=True)
flow160 = flow_all[flow_all[&#39;origin&#39;].isin(list160) &amp;amp; flow_all[&#39;destination&#39;].isin(list160)].reset_index(drop=True)

distances5 = distances_all[distances_all[&#39;origin&#39;].isin(list5) &amp;amp; distances_all[&#39;destination&#39;].isin(list5)].reset_index(drop=True)
distances10 = distances_all[distances_all[&#39;origin&#39;].isin(list10) &amp;amp; distances_all[&#39;destination&#39;].isin(list10)].reset_index(drop=True)
distances20 = distances_all[distances_all[&#39;origin&#39;].isin(list20) &amp;amp; distances_all[&#39;destination&#39;].isin(list20)].reset_index(drop=True)
distances40 = distances_all[distances_all[&#39;origin&#39;].isin(list40) &amp;amp; distances_all[&#39;destination&#39;].isin(list40)].reset_index(drop=True)
distances80 = distances_all[distances_all[&#39;origin&#39;].isin(list80) &amp;amp; distances_all[&#39;destination&#39;].isin(list80)].reset_index(drop=True)
distances160 = distances_all[distances_all[&#39;origin&#39;].isin(list160) &amp;amp; distances_all[&#39;destination&#39;].isin(list160)].reset_index(drop=True)

li_ommited = [1,4,5,9,13,15,17,26,27,28,34]

flow5_ommited = flow5.drop(flow5.index[li_ommited]).reset_index(drop=True)
flow10_ommited = flow10.drop(flow10.index[li_ommited]).reset_index(drop=True)
flow20_ommited = flow20.drop(flow20.index[li_ommited]).reset_index(drop=True)
flow40_ommited = flow40.drop(flow40.index[li_ommited]).reset_index(drop=True)
flow80_ommited = flow80.drop(flow80.index[li_ommited]).reset_index(drop=True)
flow160_ommited = flow160.drop(flow160.index[li_ommited]).reset_index(drop=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;# combine the points and distances
flow_integrated = distances_all.merge(pois_all_s, how = &#39;left&#39;, left_on=[&#39;destination&#39;], right_on=[&#39;ID_code&#39;])

#generate some weights
flow_integrated[&#39;weight&#39;] = pd.Series( np.random.randint(10,1500, size=len(flow_integrated)) # here - and 0 represent flows that does not exist
                                     )
# reorder
flow_integrated = flow_integrated.loc[:,[&#39;origin&#39;,&#39;destination&#39;,&#39;great_circle_dist&#39;,&#39;weight&#39;,&#39;mass&#39;]]

# build short datasets
flow5_integrated = flow_integrated[flow_integrated[&#39;origin&#39;].isin(list5) &amp;amp; flow_integrated[&#39;destination&#39;].isin(list5)].reset_index(drop=True)
flow10_integrated = flow_integrated[flow_integrated[&#39;origin&#39;].isin(list10) &amp;amp; flow_integrated[&#39;destination&#39;].isin(list10)].reset_index(drop=True)
flow20_integrated = flow_integrated[flow_integrated[&#39;origin&#39;].isin(list20) &amp;amp; flow_integrated[&#39;destination&#39;].isin(list20)].reset_index(drop=True)
flow40_integrated = flow_integrated[flow_integrated[&#39;origin&#39;].isin(list40) &amp;amp; flow_integrated[&#39;destination&#39;].isin(list40)].reset_index(drop=True)
flow80_integrated = flow_integrated[flow_integrated[&#39;origin&#39;].isin(list80) &amp;amp; flow_integrated[&#39;destination&#39;].isin(list80)].reset_index(drop=True)
flow160_integrated = flow_integrated[flow_integrated[&#39;origin&#39;].isin(list160) &amp;amp; flow_integrated[&#39;destination&#39;].isin(list160)].reset_index(drop=True)

flow5_integrated[&#39;great_circle_dist&#39;] = round(flow5_integrated[&#39;great_circle_dist&#39;],0)
flow10_integrated[&#39;great_circle_dist&#39;] = round(flow10_integrated[&#39;great_circle_dist&#39;],0)
flow20_integrated[&#39;great_circle_dist&#39;] = round(flow20_integrated[&#39;great_circle_dist&#39;],0)
flow40_integrated[&#39;great_circle_dist&#39;] = round(flow40_integrated[&#39;great_circle_dist&#39;],0)
flow80_integrated[&#39;great_circle_dist&#39;] = round(flow80_integrated[&#39;great_circle_dist&#39;],0)
flow160_integrated[&#39;great_circle_dist&#39;] = round(flow160_integrated[&#39;great_circle_dist&#39;],0)

flow5_integrated.head()
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;style scoped&gt;
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/style&gt;&lt;/p&gt;
&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;
  &lt;thead&gt;
    &lt;tr style=&#34;text-align: right;&#34;&gt;
      &lt;th&gt;&lt;/th&gt;
      &lt;th&gt;origin&lt;/th&gt;
      &lt;th&gt;destination&lt;/th&gt;
      &lt;th&gt;great_circle_dist&lt;/th&gt;
      &lt;th&gt;weight&lt;/th&gt;
      &lt;th&gt;mass&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;th&gt;0&lt;/th&gt;
      &lt;td&gt;A91120&lt;/td&gt;
      &lt;td&gt;A91120&lt;/td&gt;
      &lt;td&gt;0.0&lt;/td&gt;
      &lt;td&gt;906&lt;/td&gt;
      &lt;td&gt;1039&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;1&lt;/th&gt;
      &lt;td&gt;A91120&lt;/td&gt;
      &lt;td&gt;A99931&lt;/td&gt;
      &lt;td&gt;5.0&lt;/td&gt;
      &lt;td&gt;585&lt;/td&gt;
      &lt;td&gt;993&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;2&lt;/th&gt;
      &lt;td&gt;A91120&lt;/td&gt;
      &lt;td&gt;A99986&lt;/td&gt;
      &lt;td&gt;3.0&lt;/td&gt;
      &lt;td&gt;264&lt;/td&gt;
      &lt;td&gt;600&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;3&lt;/th&gt;
      &lt;td&gt;A91120&lt;/td&gt;
      &lt;td&gt;L81002&lt;/td&gt;
      &lt;td&gt;30.0&lt;/td&gt;
      &lt;td&gt;1495&lt;/td&gt;
      &lt;td&gt;1408&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;4&lt;/th&gt;
      &lt;td&gt;A91120&lt;/td&gt;
      &lt;td&gt;L81004&lt;/td&gt;
      &lt;td&gt;15.0&lt;/td&gt;
      &lt;td&gt;1135&lt;/td&gt;
      &lt;td&gt;762&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h4 id=&#34;apply-the-functions&#34;&gt;Apply the functions&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
# for 05 dataset function 3
list_AFED = []

# 5
start = timer()
A_ij = []
for idx in flow5_integrated.index:
    A = AFED(flow_df=flow5_integrated, row_index=idx)
    A_ij.append(A)
end = timer()
t = (end - start)
list_AFED.append(t)

# 10
start = timer()
A_ij = []
for idx in flow10_integrated.index:
    A = AFED(flow_df=flow10_integrated, row_index=idx)
    A_ij.append(A)
end = timer()
t = (end - start)
list_AFED.append(t)

# 20
start = timer()
A_ij = []
for idx in flow20_integrated.index:
    A = AFED(flow_df=flow20_integrated, row_index=idx)
    A_ij.append(A)
end = timer()
t = (end - start)
list_AFED.append(t)

# 40
start = timer()
A_ij = []
for idx in flow40_integrated.index:
    A = AFED(flow_df=flow40_integrated, row_index=idx)
    A_ij.append(A)
end = timer()
t = (end - start)
list_AFED.append(t)

# 80
start = timer()
A_ij = []
for idx in flow80_integrated.index:
    A = AFED(flow_df=flow80_integrated, row_index=idx)
    A_ij.append(A)
end = timer()
t = (end - start)
list_AFED.append(t)

# 160
start = timer()
A_ij = []
for idx in flow160_integrated.index:
    A = AFED(flow_df=flow160_integrated, row_index=idx)
    A_ij.append(A)
end = timer()
t = (end - start)
list_AFED.append(t)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;list_AFED
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[0.26563450000000444,
 0.8232417000000112,
 3.175610599999999,
 12.360262700000007,
 54.0198017,
 303.65919140000005]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
# for 05 dataset function 3
list_AFED_MAT = []

# 5
start = timer()
flow5_mat = AFED_MAT(flow5_integrated)
end = timer()
t = (end - start)
list_AFED_MAT.append(t)

# 10
start = timer()
A_ij = []
flow10_mat = AFED_MAT(flow10_integrated)
end = timer()
t = (end - start)
list_AFED_MAT.append(t)

# 20
start = timer()
A_ij = []
flow20_mat = AFED_MAT(flow20_integrated)
end = timer()
t = (end - start)
list_AFED_MAT.append(t)

# 40
start = timer()
A_ij = []
flow40_mat = AFED_MAT(flow40_integrated)
end = timer()
t = (end - start)
list_AFED_MAT.append(t)

# 80
start = timer()
A_ij = []
flow80_mat = AFED_MAT(flow80_integrated)
end = timer()
t = (end - start)
list_AFED_MAT.append(t)

# 160
start = timer()
A_ij = []
flow160_mat = AFED_MAT(flow160_integrated)
end = timer()
t = (end - start)
list_AFED_MAT.append(t)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;time elapsed: 0.06575129999998808
time elapsed: 0.2165177999999628 
time elapsed: 0.8499411000000237   
time elapsed: 3.23107200000004   
time elapsed: 14.276894200000015
time elapsed: 93.39352159999999
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
# for 05 dataset function 3
list_AFED_VECTOR = []

# 5
start = timer()
flow5_mat = AFED_VECTOR(flow5_integrated)
end = timer()
t = (end - start)
list_AFED_VECTOR.append(t)

# 10
start = timer()
A_ij = []
flow10_mat = AFED_VECTOR(flow10_integrated)
end = timer()
t = (end - start)
list_AFED_VECTOR.append(t)

# 20
start = timer()
A_ij = []
flow20_mat = AFED_VECTOR(flow20_integrated)
end = timer()
t = (end - start)
list_AFED_VECTOR.append(t)

# 40
start = timer()
A_ij = []
flow40_mat = AFED_VECTOR(flow40_integrated)
end = timer()
t = (end - start)
list_AFED_VECTOR.append(t)

# 80
start = timer()
A_ij = []
flow80_mat = AFED_VECTOR(flow80_integrated)
end = timer()
t = (end - start)
list_AFED_VECTOR.append(t)

# 160
start = timer()
A_ij = []
flow160_mat = AFED_VECTOR(flow160_integrated)
end = timer()
t = (end - start)
list_AFED_VECTOR.append(t)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;time elapsed: 0.023291800000038165
time elapsed: 0.023622900000077607
time elapsed: 0.022046499999987645
time elapsed: 0.026168299999994815
time elapsed: 0.04007190000004357
time elapsed: 0.15891480000004776
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;evaluate&#34;&gt;Evaluate&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;times = pd.DataFrame({&#39;original&#39;: pd.Series(list_AFED),
                     &#39;matrices&#39;: pd.Series(list_AFED_MAT),
                    &#39;vectorized&#39;: pd.Series(list_AFED_VECTOR)})
times
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;style scoped&gt;
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/style&gt;&lt;/p&gt;
&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;
  &lt;thead&gt;
    &lt;tr style=&#34;text-align: right;&#34;&gt;
      &lt;th&gt;&lt;/th&gt;
      &lt;th&gt;original&lt;/th&gt;
      &lt;th&gt;matrices&lt;/th&gt;
      &lt;th&gt;vectorized&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;th&gt;0&lt;/th&gt;
      &lt;td&gt;0.265635&lt;/td&gt;
      &lt;td&gt;0.093522&lt;/td&gt;
      &lt;td&gt;0.023567&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;1&lt;/th&gt;
      &lt;td&gt;0.823242&lt;/td&gt;
      &lt;td&gt;0.240578&lt;/td&gt;
      &lt;td&gt;0.024067&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;2&lt;/th&gt;
      &lt;td&gt;3.175611&lt;/td&gt;
      &lt;td&gt;0.879755&lt;/td&gt;
      &lt;td&gt;0.022347&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;3&lt;/th&gt;
      &lt;td&gt;12.360263&lt;/td&gt;
      &lt;td&gt;3.258259&lt;/td&gt;
      &lt;td&gt;0.026568&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;4&lt;/th&gt;
      &lt;td&gt;54.019802&lt;/td&gt;
      &lt;td&gt;14.310321&lt;/td&gt;
      &lt;td&gt;0.041264&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;5&lt;/th&gt;
      &lt;td&gt;303.659191&lt;/td&gt;
      &lt;td&gt;93.476836&lt;/td&gt;
      &lt;td&gt;0.166047&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;fig, ax = plt.subplots(figsize=(15, 10))
(times.iloc[:,0:3]/60).plot(ax = ax,  linewidth=5)
plt.xlabel(&amp;quot;Number of observations/flows&amp;quot;,size=15)
plt.ylabel(&amp;quot;Minutes&amp;quot;,size=15)
plt.xticks(times.index, (str(len(flow5)), str(len(flow10)), str(len(flow20)), str(len(flow40)),str(len(flow80)), str(len(flow160))),size=15)
plt.yticks(size=15)
plt.legend( prop={&#39;size&#39;: 15})
plt.show();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;./gsoc/index_33_0.png&#34; alt=&#34;png&#34;&gt;&lt;/p&gt;
&lt;p&gt;From the graph above you can see that with each new finction the speed improved. There is very little difference in the speed if your data has less then 200 observations/flows (or 14 origins and destinations). However, when your data sets are bigger, the speed becomes significantly better with the vectorized function, then the original one. For example, 25,921 observations/flows (161 origins and destinations will take only &lt;strong&gt;17 seconds&lt;/strong&gt; to compute by vectorized function, in compare with the original one that takes close to &lt;strong&gt;6 minutes&lt;/strong&gt; to compute.&lt;/p&gt;
&lt;h3 id=&#34;7-changes-after-review&#34;&gt;7. Changes after review&lt;/h3&gt;
&lt;p&gt;After the mentors reviewed my code, there was few places for improvements.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instead of feeding dataframe in, we can just feed numpy arrays. This simply makes things easier for the user as less thing can go wrong with the input.&lt;/li&gt;
&lt;li&gt;We can create an error message which checks that all the inputs are the same length. This will validate that the input is correct.&lt;/li&gt;
&lt;li&gt;There is also some unnecessary stuff to delete.&lt;/li&gt;
&lt;li&gt;The test could use more appropriate function.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;final-version-of-function&#34;&gt;Final version of function&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def Accessibility(nodes, distances, weights, masses, all_destinations=False):
    
    # convert numbers to integers
    distances = np.array(distances.astype(int))
    weights = np.array(weights.astype(int))
    masses = np.array(masses.astype(int))
    nodes = np.array(nodes)
    
    # define error
    if len(distances) != len(weights) != len(masses) != len(nodes):
        raise ValueError(&amp;quot;One of the input array is different length then the others, but they should all be the same length. See notebook example if you are unsure what the input should look like &amp;quot;)
    
    # define number of rows
    nrows= len(nodes)
    uniques = len(np.unique(np.array(nodes)))
    
    # create binary for weight
    v_bin =  np.ones(nrows)
    weights[np.isnan(weights)] = 0
    v_bin[weights &amp;lt;= 0] = 0
    
    # define the base matrices
    distance = distances.reshape(uniques,uniques)
    mass =masses.reshape(uniques,uniques).T
    exists = v_bin.reshape(uniques,uniques)
    
      
    # define the identity array
    idn = np.identity(uniques) 
    idn = np.where((idn==1), 0, 1)
    idn = np.concatenate(uniques * [idn.ravel()], axis = 0
                        ).reshape(uniques,uniques,uniques
                                 ).T

    # multiply the distance by mass
    dm = distance * mass
    
    # combine all matrices for either all or existing destinations
    if all_destinations:
        output = idn * (nrows * [dm])
        
    else:
        output = (np.concatenate(uniques * [exists], axis = 0
                                ).reshape(uniques,uniques,uniques
                                         ).T
                 ) * idn * (uniques * [dm]
                           )
    
    # get the sum and covert to series
    output = (np.sum(output,axis = 1
                    ) 
             ).reshape(nrows
                      ).T
    
    
    return output

&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;final-version-of-test&#34;&gt;Final version of test&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;class AccessibilityTest(unittest.TestCase):
    
    def test_accessibility(self):
        flow = _generate_dummy_flows()
        flow = flow.loc[:,[&#39;origin_ID&#39;, &#39;destination_ID&#39;,&#39;distances&#39;, &#39;volume_in_unipartite&#39;,&#39;dest_masses&#39;,&#39;results_all=False&#39;]]
        flow[&#39;acc_uni&#39;] = Accessibility(nodes = flow[&#39;origin_ID&#39;],  distances = flow[&#39;distances&#39;], weights = flow[&#39;volume_in_unipartite&#39;], masses = flow[&#39;dest_masses&#39;], all_destinations=False)
        
        np.testing.assert_array_equal(flow[&#39;results_all=False&#39;].all(), flow[&#39;acc_uni&#39;].all())

if __name__ == &#39;__main__&#39;:
    unittest.main()
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
  </channel>
</rss>
