<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on Atharva Khare</title>
        <link>https://khare.dev/posts/</link>
        <description>Recent content in Posts on Atharva Khare</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <copyright>&lt;a href=&#34;https://creativecommons.org/licenses/by-nc/4.0/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;CC BY-NC 4.0&lt;/a&gt;</copyright>
        <lastBuildDate>Mon, 26 Aug 2019 00:32:29 -0700</lastBuildDate>
        <atom:link href="https://khare.dev/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>GSoC 2019 final report</title>
            <link>https://khare.dev/posts/2019/08/gsoc-2019-final-report/</link>
            <pubDate>Mon, 26 Aug 2019 00:32:29 -0700</pubDate>
            
            <guid>https://khare.dev/posts/2019/08/gsoc-2019-final-report/</guid>
            <description>After 14 issues, 17 PRs, and over 40 commits later, GSoC has finally come to an end. Here is a short demo on the major features added to Pharo&amp;rsquo;s libraries:
  Most of the Phase 3 was spent on creating the DataSet library, tweaking it, and creating blog posts.
A list of issues and PRs created uptil Phase 2 are:  
Here is the list of blog posts describing the contributions to Pharo:</description>
            <content type="html"><![CDATA[

<p>After 14 issues, 17 PRs, and over 40 commits later, GSoC has finally come to an end. Here is a short demo on the major features added to Pharo&rsquo;s libraries:</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="//www.youtube.com/embed/1uFeaP448gQ" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>

<p>Most of the Phase 3 was spent on creating the <a href="https://github.com/AtharvaKhare/DataSet" target="_blank">DataSet library</a>, tweaking it, and creating blog posts.</p>

<p>A list of issues and PRs created uptil Phase 2 are:
<div style="overflow:hidden;">
<iframe style="width:100%;height:500px;" src="https://docs.google.com/spreadsheets/d/e/2PACX-1vRcs2liTzBN07t5bUNWUokVhBdCCW09jT5eeyfzHzm9QBmdgO7mOFDTdbTlFMQzYfcI9PApdDMpcuW1/pubhtml?widget=true&amp;headers=false"></iframe>
</div></p>

<p>Here is the list of blog posts describing the contributions to Pharo:</p>

<ol>
<li><a href="https://khare.dev/posts/2019/05/gsoc-2019-extending-dataframe-library-for-pharo-consortium/" target="_blank">Summary of the proposal</a></li>
<li><a href="https://khare.dev/posts/2019/05/gsoc-community-bonding-period-summary/" target="_blank">Community bonding period</a></li>
<li><a href="https://khare.dev/posts/2019/06/gsoc-2019-phase-1-progress/" target="_blank">Phase 1 progress</a></li>
<li><a href="https://khare.dev/posts/2019/07/gsoc-2019-phase-2-progress/" target="_blank">Phase 2 progress</a></li>
<li>Phase 3 blogs: <a href="https://khare.dev/posts/2019/08/introducing-the-dataset-library-for-pharo/" target="_blank">DataSet library</a>, <a href="https://khare.dev/posts/2019/08/dataframe-io-current-performance/" target="_blank">DataFrame IO comparison</a> and <a href="https://khare.dev/posts/2019/07/joins-in-pharos-dataframe-library/" target="_blank">DataFrame Joins</a>.</li>
</ol>

<p>You can view all the blog posts at <a href="https://khare.dev/tags/gsoc-progress" target="_blank">this link</a>.</p>

<h2 id="future-work">Future work</h2>

<h3 id="improving-dataframetypedetector">Improving DataFrameTypeDetector</h3>

<p>As described in a previous blog post, improving the <code>DataFrameTypeDetector</code> class will lead to faster load times of csv files.</p>

<h3 id="dataset-library">DataSet library</h3>

<p>Identifing and adding additional datasets to the library. Also, modifing the library such that adding new datasets would require adding minimal amount of code.</p>

<h3 id="tutorials">Tutorials</h3>

<p>Creating data analysis tutorials using the new DataSet library, DataFrame and PolyMath together.</p>

<h3 id="additional-improvements-to-dataframe">Additional improvements to DataFrame</h3>

<p>Refer to the <a href="https://github.com/PolyMathOrg/DataFrame/blob/master/roadmap.md" target="_blank">roadmap of the DataFrame library</a>. Features like database IO, time series, spec editor etc need to be added to the library.</p>
]]></content>
        </item>
        
        <item>
            <title>Introducing the DataSet library for Pharo</title>
            <link>https://khare.dev/posts/2019/08/introducing-the-dataset-library-for-pharo/</link>
            <pubDate>Mon, 05 Aug 2019 20:13:15 -0700</pubDate>
            
            <guid>https://khare.dev/posts/2019/08/introducing-the-dataset-library-for-pharo/</guid>
            <description>As a part of GSoC, I have added a new library to fetch popular datasets in Pharo - https://github.com/AtharvaKhare/DataSet. Currently, it includes following datasets:
 Boston house prices dataset Breast cancer wisconsin (diagnostic) dataset Diabetes dataset Optical recognition of handwritten digits dataset Iris plants dataset MNIST testing data Wine recognition dataset  Any requests for additional datasets to be added can be put at this issue.
Structure and Working of the library The DataSetFiles repo has datasets in csv format, and the DataSet library has the code to fetch and convert it as a DataFrame object.</description>
            <content type="html"><![CDATA[

<p>As a part of GSoC, I have added a new library to fetch popular datasets in Pharo - <a href="https://github.com/AtharvaKhare/DataSet" target="_blank">https://github.com/AtharvaKhare/DataSet</a>. Currently, it includes following datasets:</p>

<ol>
<li>Boston house prices dataset</li>
<li>Breast cancer wisconsin (diagnostic) dataset</li>
<li>Diabetes dataset</li>
<li>Optical recognition of handwritten digits dataset</li>
<li>Iris plants dataset</li>
<li>MNIST testing data</li>
<li>Wine recognition dataset</li>
</ol>

<p>Any requests for additional datasets to be added can be put at <a href="https://github.com/AtharvaKhare/DataSet/issues/1" target="_blank">this issue</a>.</p>

<h3 id="structure-and-working-of-the-library">Structure and Working of the library</h3>

<p>The <a href="https://github.com/AtharvaKhare/DataSetFiles/" target="_blank">DataSetFiles</a> repo has datasets in csv format, and the DataSet library has the code to fetch and convert it as a DataFrame object.</p>

<p>To load a dataset, you use <code>DataSet loadXYZ</code>. For example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">df</span> <span class="o">:=</span> <span class="nc">DataSet</span> <span class="nf">loadBoston</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>This checks the filesystem to see if the dataset is available, and downloads if it isn&rsquo;t present using <code>DataSet downloadXYZ</code> method.
The downloaded files are stored at <code>data</code> folder in the root of repo stored in the filesystem.</p>

<p>To download all the datasets in advance, use:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nc">DataSet</span> <span class="nf">downloadAll</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>This downloads the datasets that have not yet been downloaded.</p>

<h3 id="future-work">Future work</h3>

<p>A possible improvement to the library would be storing a list of datasets in DataSetFiles, and modifing DataSet library such that user will be able to dynamically fetch available datasets and download whichever is needed. If you have thoughts/suggestions regarding this or any improvements, email me or create an issue on Github!</p>
]]></content>
        </item>
        
        <item>
            <title>DataFrame IO - current performance</title>
            <link>https://khare.dev/posts/2019/08/dataframe-io-current-performance/</link>
            <pubDate>Sat, 03 Aug 2019 10:02:17 -0700</pubDate>
            
            <guid>https://khare.dev/posts/2019/08/dataframe-io-current-performance/</guid>
            <description>Currently there are two ways to read/write dataframes to files: csv and json. While developing the new DataSet library, I benchmarked the reading speeds of different methods. Following is the result of benchmark of reading speeds on my machine, in milliseconds: table.tableizer-table { border: 1px solid #CCC; } .tableizer-table td { padding: 4px; margin: 3px; border: 1px solid #CCC; } .tableizer-table th { color: #FFF; font-weight: bold; }  Datasetcsvjsonsubtypes:&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;splitrecordsindexcolumnsvalues Iris4.</description>
            <content type="html"><![CDATA[<p>Currently there are two ways to read/write dataframes to files: csv and json. While developing the new <a href="https://github.com/AtharvaKhare/DataSet" target="_blank">DataSet library</a>, I benchmarked the reading speeds of different methods. Following is the result of benchmark of reading speeds on my machine, in milliseconds:
<style type="text/css">
    table.tableizer-table {
        border: 1px solid #CCC;
    }
    .tableizer-table td {
        padding: 4px;
        margin: 3px;
        border: 1px solid #CCC;
    }
    .tableizer-table th {
        color: #FFF;
        font-weight: bold;
    }
</style>
<table class="tableizer-table">
<thead><tr class="tableizer-firstrow"><th align="center">Dataset</th><th align="center">csv</th><th align="center">json</th><th>subtypes:</th><th>&nbsp;</th><th>&nbsp;</th><th>&nbsp;</th></tr></thead><tbody>
 <tr><td>&nbsp;</td><td></td><td>split</td><td>records</td><td>index</td><td>columns</td><td>values</td></tr>
 <tr><td>Iris</td><td>4.57</td><td>2.95</td><td>5.19</td><td>18.08</td><td>8.92</td><td>2.69</td></tr>
 <tr><td>Boston</td><td>42.7</td><td>18.59</td><td>43.6</td><td>121.1</td><td>193</td><td>18.59</td></tr>
 <tr><td>Digits</td><td>663.5</td><td>277.9</td><td>657</td><td>1889.2</td><td>8606.5</td><td>257.9</td></tr>
 <tr><td>MNIST test (10k rows)</td><td>49490</td><td>13491</td><td>49019</td><td>NA</td><td>NA</td><td>13360</td></tr>
 <tr><td>MNIST train (60k rows)</td><td>303505</td><td>106291</td><td>NA</td><td>NA</td><td>NA</td><td>102917</td></tr>
</tbody></table></p>

<p>As you can see, the fastest method is json with <code>orient='split'</code>, since the format is close to the object&rsquo;s internal representation. Next fastset is the csv and json <code>orient='records'</code>. Others are significantly slower.</p>

<p><img src="/images/dataframe_io/csv_profile.png" alt="CSV reading profile" /></p>

<p>The primary reason for slowdown in reading csv is the <code>DataFrameTypeDetector detectTypesAndConvert</code> method. It runs through all elements in all columns and determines the type of that column. It may run multiple times on a column, causing a significant slowdown for large dataframes. On large datasets, it has slowdown of at least 85%. Modifying this method to query on only a few elements will cause a significant performance boost.</p>

<p><img src="/images/dataframe_io/json_profile.png" alt="JSON reading profile" /></p>

<p>Similarly, for reading json using the fastest method available (<code>orient: split</code>), <code>detectTypesAndConvert</code> method causes slowdown.</p>

<p><br></p>

<p><strong>TL;DR</strong>: Improving <code>DataFrameTypeDetector</code> will massively increase reading speeds, by 50% to 90% for large files.</p>
]]></content>
        </item>
        
        <item>
            <title>Joins in Pharo&#39;s DataFrame library</title>
            <link>https://khare.dev/posts/2019/07/joins-in-pharos-dataframe-library/</link>
            <pubDate>Tue, 30 Jul 2019 14:53:53 -0700</pubDate>
            
            <guid>https://khare.dev/posts/2019/07/joins-in-pharos-dataframe-library/</guid>
            <description>One of the newest feature added to the DataFrame library is the support for joins. This post covers joining and appending dataframes.
Joining DataFrames The library supports SQL-type joins: inner, outer, left, right.
Joining using rowNames Joining dataframes using rowNames is faster vs an arbitary column, since rowNames are unique leading to faster construction of output dataframe.
Consider two dataframes with course names as rowName:
1 2 3 4 5 6 7 8 9 10 11 12 13 14  df1 := DataFrame withRows: #( (&amp;#39;RTH-01&amp;#39;, &amp;#39;11:00 - 01:00&amp;#39;) (&amp;#39;RTH-02&amp;#39;, &amp;#39;04:00 - 05:50&amp;#39;) (&amp;#39;THH-02&amp;#39;, &amp;#39;09:00 - 10:30&amp;#39;) ) rowNames: #(&amp;#39;CS-10&amp;#39; &amp;#39;CS-11&amp;#39; &amp;#39;CS-13&amp;#39;) columnNames: #(Location Timing).</description>
            <content type="html"><![CDATA[

<p>One of the newest feature added to the DataFrame library is <a href="https://github.com/PolyMathOrg/DataFrame/pull/110" target="_blank">the support for joins</a>. This post covers joining and appending dataframes.</p>

<h2 id="joining-dataframes">Joining DataFrames</h2>

<p>The library supports SQL-type joins: inner, outer, left, right.</p>

<h3 id="joining-using-rownames">Joining using rowNames</h3>

<p>Joining dataframes using rowNames is faster vs an arbitary column, since rowNames are unique leading to faster construction of output dataframe.</p>

<p>Consider two dataframes with course names as rowName:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">df1</span> <span class="o">:=</span> <span class="nc">DataFrame</span> <span class="nf">withRows:</span> <span class="ss">#(</span>
	<span class="ss">(</span><span class="s">&#39;RTH-01&#39;</span><span class="ss">,</span> <span class="s">&#39;11:00 - 01:00&#39;</span><span class="ss">)</span>
	<span class="ss">(</span><span class="s">&#39;RTH-02&#39;</span><span class="ss">,</span> <span class="s">&#39;04:00 - 05:50&#39;</span><span class="ss">)</span>
	<span class="ss">(</span><span class="s">&#39;THH-02&#39;</span><span class="ss">,</span> <span class="s">&#39;09:00 - 10:30&#39;</span><span class="ss">)</span>
	<span class="ss">)</span>
	<span class="nf">rowNames:</span> <span class="ss">#(</span><span class="s">&#39;CS-10&#39;</span> <span class="s">&#39;CS-11&#39;</span> <span class="s">&#39;CS-13&#39;</span><span class="ss">)</span>
	<span class="nf">columnNames:</span> <span class="ss">#(Location</span> <span class="ss">Timing)</span><span class="p">.</span>

<span class="nv">df2</span> <span class="o">:=</span> <span class="nc">DataFrame</span> <span class="nf">withRows:</span> <span class="ss">#(</span>
	<span class="ss">(</span><span class="s">&#39;Prof. Foo&#39;</span> <span class="m">50</span><span class="ss">)</span>
	<span class="ss">(</span><span class="s">&#39;Prof. Bar&#39;</span> <span class="m">45</span><span class="ss">)</span>
	<span class="ss">)</span>
	<span class="nf">rowNames:</span> <span class="ss">#(</span><span class="s">&#39;CS-11&#39;</span> <span class="s">&#39;CS-12&#39;</span><span class="ss">)</span>
	<span class="nf">columnNames:</span> <span class="ss">#(Instructor</span> <span class="ss">Capacity)</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p><style>
    .demo {
        border:1px solid #C0C0C0;
        border-collapse:collapse;
        padding:5px;
    }
    .demo th {
        border:1px solid #C0C0C0;
        padding:5px;
        text-align:center;
    }
    .demo td {
        border:1px solid #C0C0C0;
        padding:5px;
        text-align:center;
    }
</style>
<table class="demo">
    <caption>df1</caption>
    <thead>
    <tr>
        <th><br></th>
        <th>Location<br></th>
        <th>Timing<br></th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;CS-10</td>
        <td>&nbsp;RTH-01</td>
        <td>&nbsp;11:00 - 01:00<br></td>
    </tr>
    <tr>
        <td>&nbsp;CS-11</td>
        <td>&nbsp;RTH-02</td>
        <td>&nbsp;04:00 - 05:50<br></td>
    </tr>
    <tr>
        <td>&nbsp;CS-13</td>
        <td>&nbsp;THH-02</td>
        <td>&nbsp;09:00 - 10:30<br></td>
    </tr>
    </tbody>
</table></p>

<table class="demo">
    <caption>df2</caption>
    <thead>
    <tr>
        <th><br></th>
        <th>Instructor<br></th>
        <th>Capacity<br></th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;CS-11</td>
        <td>&nbsp;Prof. Foo<br></td>
        <td>&nbsp;50<br></td>
    </tr>
    <tr>
        <td>&nbsp;CS-12</td>
        <td>&nbsp;Prof. Bar<br></td>
        <td>&nbsp;45<br></td>
    </tr>
    </tbody>
</table>

<p><strong>Inner join</strong> combines two dataframes based on their intersection (in this case, the rowNames). Doing <code>df1 innerJoin: df2</code> would output intersecting rows between dataframes (CS-11) with columns combined.</p>

<table class="demo">
    <caption>df1 innerJoin: df2</caption>
    <thead>
    <tr>
        <th><br></th>
        <th>Location<br></th>
        <th>Timing<br></th>
        <th>Instructor</th>
        <th>Capacity</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;CS-11</td>
        <td>&nbsp;RTH-02<br></td>
        <td>&nbsp;04:00 - 05:50<br></td>
        <td>&nbsp;Prof. Foo<br></td>
        <td>&nbsp;50</td>
    </tr>
    </tbody>
</table>

<p><strong>Outer join</strong> includes all the rows in the dataframe, and adds <code>nil</code> to the missing columns.</p>

<table class="demo">
    <caption>df1 outerJoin: df2</caption>
    <thead>
    <tr>
        <th><br></th>
        <th>Location<br></th>
        <th>Timing<br></th>
        <th>Instructor</th>
        <th>Capacity</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;CS-10</td>
        <td>&nbsp;RTH-01<br></td>
        <td>&nbsp;11:00 - 01:00</td>
        <td>&nbsp;nil<br></td>
        <td>&nbsp;nil</td>
    </tr>
    <tr>
        <td>CS-11 <br></td>
        <td>&nbsp; RTH-02</td>
        <td>04:00 - 05:50 <br></td>
        <td>&nbsp;Prof. Foo<br></td>
        <td>&nbsp;50</td>
    </tr>
    <tr>
        <td>&nbsp;CS-12</td>
        <td>&nbsp; nil<br></td>
        <td>&nbsp;nil</td>
        <td>Prof. Bar <br></td>
        <td>&nbsp;45</td>
    </tr>
    <tr>
        <td>&nbsp;CS-13</td>
        <td>&nbsp; THH-02</td>
        <td>&nbsp;09:00 - 10:30<br></td>
        <td>nil <br></td>
        <td>&nbsp;nil</td>
    </tr>
    <tbody>
</table>

<p>Left join includes all the rows present in the left dataframe, while adding <code>nil</code> in places with missing data. CS-10, CS-11 and CS13 will be present in the output of <code>df1 leftJoin: df2</code>, with <code>nil</code> in column Instructor and Capacity for rows CS-10 and CS-13.
<table class="demo">
    <caption>df1 leftJoin: df2</caption>
    <thead>
    <tr>
        <th><br></th>
        <th>Location<br></th>
        <th>Timing<br></th>
        <th>Instructor</th>
        <th>Capacity</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;CS-10</td>
        <td>&nbsp;RTH-01<br></td>
        <td>&nbsp;11:00 - 01:00</td>
        <td>&nbsp;nil<br></td>
        <td>&nbsp;nil</td>
    </tr>
    <tr>
        <td>CS-11 <br></td>
        <td>&nbsp; RTH-02</td>
        <td>04:00 - 05:50 <br></td>
        <td>&nbsp;Prof. Foo<br></td>
        <td>&nbsp;50</td>
    </tr>
    <tr>
        <td>&nbsp;CS-13</td>
        <td>&nbsp; THH-02</td>
        <td>&nbsp;09:00 - 10:30<br></td>
        <td>nil <br></td>
        <td>&nbsp;nil</td>
    </tr>
    <tbody>
</table></p>

<p><strong>Right join</strong> is similar to left join, except it preserves the rows in the right dataframe.
<table class="demo">
    <caption>df1 rightJoin: df2</caption>
    <thead>
    <tr>
        <th><br></th>
        <th>Location<br></th>
        <th>Timing<br></th>
        <th>Instructor</th>
        <th>Capacity</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>CS-11 <br></td>
        <td>&nbsp; RTH-02</td>
        <td>04:00 - 05:50 <br></td>
        <td>&nbsp;Prof. Foo<br></td>
        <td>&nbsp;50</td>
    </tr>
    <tr>
        <td>&nbsp;CS-12</td>
        <td>&nbsp; nil<br></td>
        <td>&nbsp;nil</td>
        <td>Prof. Bar <br></td>
        <td>&nbsp;45</td>
    </tr>
    <tbody>
</table></p>

<h3 id="joining-using-arbitary-column">Joining using arbitary column</h3>

<p>The API for joining arbitary column is:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">df</span> <span class="nf">innerJoin:</span> <span class="nv">df2</span> <span class="nf">onLeft:</span> <span class="s">&#39;LeftCol&#39;</span> <span class="nf">onRight:</span> <span class="s">&#39;RightCol&#39;</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">outerJoin:</span> <span class="nv">df2</span> <span class="nf">onLeft:</span> <span class="s">&#39;LeftCol&#39;</span> <span class="nf">onRight:</span> <span class="s">&#39;RightCol&#39;</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">leftJoin:</span> <span class="nv">df2</span> <span class="nf">onLeft:</span> <span class="s">&#39;LeftCol&#39;</span> <span class="nf">onRight:</span> <span class="s">&#39;RightCol&#39;</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">rightJoin:</span> <span class="nv">df2</span> <span class="nf">onLeft:</span> <span class="s">&#39;LeftCol&#39;</span> <span class="nf">onRight:</span> <span class="s">&#39;RightCol&#39;</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>Consider similar dataframes as before, except with course names being it&rsquo;s own column:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">df1</span> <span class="o">:=</span> <span class="nc">DataFrame</span> <span class="nf">withRows:</span> <span class="ss">#(</span>
	<span class="ss">(</span><span class="s">&#39;CS-10&#39;</span> <span class="s">&#39;RTH-01&#39;</span><span class="ss">,</span> <span class="s">&#39;11:00 - 01:00&#39;</span><span class="ss">)</span>
	<span class="ss">(</span><span class="s">&#39;CS-11&#39;</span> <span class="s">&#39;RTH-02&#39;</span><span class="ss">,</span> <span class="s">&#39;04:00 - 05:50&#39;</span><span class="ss">)</span>
	<span class="ss">(</span><span class="s">&#39;CS-13&#39;</span> <span class="s">&#39;THH-02&#39;</span><span class="ss">,</span> <span class="s">&#39;09:00 - 10:30&#39;</span><span class="ss">)</span>
	<span class="ss">)</span>
	<span class="nf">columnNames:</span> <span class="ss">#(Course</span> <span class="ss">Location</span> <span class="ss">Timing)</span><span class="p">.</span>

<span class="nv">df2</span> <span class="o">:=</span> <span class="nc">DataFrame</span> <span class="nf">withRows:</span> <span class="ss">#(</span>
	<span class="ss">(</span><span class="s">&#39;CS-11&#39;</span> <span class="s">&#39;Prof. Foo&#39;</span> <span class="m">50</span><span class="ss">)</span>
	<span class="ss">(</span><span class="s">&#39;CS-12&#39;</span> <span class="s">&#39;Prof. Bar&#39;</span> <span class="m">45</span><span class="ss">)</span>
	<span class="ss">)</span>
	<span class="nf">columnNames:</span> <span class="ss">#(Course</span> <span class="ss">Instructor</span> <span class="ss">Capacity)</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>Joining using left join would lead to:
<table class="demo">
    <caption><code>df1 leftJoin: df2 onLeft: 'Course' onRight: 'CourseName'</code></caption>
    <thead>
    <tr>
        <th><br></th>
        <th>Course<br></th>
        <th>Location<br></th>
        <th>Timing</th>
        <th>CourseName<br></th>
        <th>Instructor</th>
        <th>Capacity<br></th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;1 <br></td>
        <td>CS-10 <br></td>
        <td>RTH-01 <br></td>
        <td>&nbsp;11:00 - 01:00<br></td>
        <td>&nbsp;nil</td>
        <td>nil <br></td>
        <td>nil <br></td>
    </tr>
    <tr>
        <td>&nbsp;2</td>
        <td>CS-11</td>
        <td>&nbsp;RTH-02</td>
        <td>04:00 - 05:50 <br></td>
        <td>CS-11<br></td>
        <td>&nbsp;Prof. Foo<br></td>
        <td>&nbsp;50</td>
    </tr>
    <tr>
        <td>&nbsp;3</td>
        <td>CS-13<br></td>
        <td>&nbsp;THH-02</td>
        <td>09:00 - 10:30 <br></td>
        <td>nil <br></td>
        <td>&nbsp;nil</td>
        <td>&nbsp;nil</td>
    </tr>
    </tbody>
</table></p>

<h3 id="joining-datafames-with-same-column-names">Joining datafames with same column names</h3>

<p>If the two dataframes have conflicting column names, it will be replaced by <code>_x</code> and <code>_y</code>. For instance, in the previous example, if both dataframes has column names <code>Course</code>, output dataframe would be:
<table class="demo">
    <caption><code>df1 leftJoin: df2 onLeft: 'Course' onRight: 'Course'</code></caption>
    <thead>
    <tr>
        <th><br></th>
        <th>Course_x<br></th>
        <th>Location<br></th>
        <th>Timing</th>
        <th>Course_y<br></th>
        <th>Instructor</th>
        <th>Capacity<br></th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;1 <br></td>
        <td>CS-10 <br></td>
        <td>RTH-01 <br></td>
        <td>&nbsp;11:00 - 01:00<br></td>
        <td>&nbsp;nil</td>
        <td>nil <br></td>
        <td>nil <br></td>
    </tr>
    <tr>
        <td>&nbsp;2</td>
        <td>CS-11</td>
        <td>&nbsp;RTH-02</td>
        <td>04:00 - 05:50 <br></td>
        <td>CS-11<br></td>
        <td>&nbsp;Prof. Foo<br></td>
        <td>&nbsp;50</td>
    </tr>
    <tr>
        <td>&nbsp;3</td>
        <td>CS-13<br></td>
        <td>&nbsp;THH-02</td>
        <td>09:00 - 10:30 <br></td>
        <td>nil <br></td>
        <td>&nbsp;nil</td>
        <td>&nbsp;nil</td>
    </tr>
    </tbody>
</table></p>

<h3 id="appending-dataframes">Appending dataframes</h3>

<p>Consider two dataframes:</p>

<table class="demo">
    <caption>df1</caption>
    <thead>
    <tr>
    <th></th>
        <th>Course</th>
        <th>Location<br></th>
        <th>Timing</th>
        <th>Instructor</th>
        <th>Capacity</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;1 <br></td>
        <td>&nbsp;CS-10</td>
        <td>&nbsp;RTH-01</td>
        <td>&nbsp;11:00 - 01:00</td>
        <td>&nbsp;Prof. Foo<br></td>
        <td>&nbsp;50</td>
    </tr>
    <tr>
        <td>&nbsp;2</td>
        <td>&nbsp;CS-11</td>
        <td>&nbsp;RTH-02</td>
        <td>&nbsp;04:00 - 05:50<br></td>
        <td>&nbsp;Prof. Bar<br></td>
        <td>&nbsp;50</td>
    </tr>
    <tr>
        <td>&nbsp;3</td>
        <td>&nbsp;CS-12</td>
        <td>&nbsp;THH-01</td>
        <td>&nbsp;TBA</td>
        <td>&nbsp;TBA</td>
        <td>&nbsp;45</td>
    </tr>
    </tbody>
</table>

<p><br></p>

<table class="demo">
    <caption>df2</caption>
    <thead>
    <tr>
    <th></th>
        <th>Course</th>
        <th>Location<br></th>
        <th>Timing</th>
        <th>Instructor</th>
        <th>Capacity</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>&nbsp;4</td>
        <td>&nbsp;CS-13</td>
        <td>&nbsp;THH-02</td>
        <td>&nbsp;09:00 - 10:30<br></td>
        <td>&nbsp;Prof. Baz<br></td>
        <td>&nbsp;30</td>
    </tr>
    </tbody>
</table>

<p>You can append/concatenate them by:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">appenDf</span> <span class="o">:=</span> <span class="nv">df1</span><span class="nf">,</span> <span class="nv">df2</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>There are a few caveats:</p>

<ol>
<li><strong>Row names should be unique</strong>. Else, the concatenation operator will not work. You can use <code>df1 rowNames: (1 to: df1 size)</code> and <code>df2 rowNames: (df1 size + 1 to: df1 size + df2 size)</code> followed by <code>df1, df2</code>.</li>
<li><strong>Order of columns should be the same</strong>. If it is not, reconstruct the dataframe.</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>GSoC 2019 Phase 2 progress</title>
            <link>https://khare.dev/posts/2019/07/gsoc-2019-phase-2-progress/</link>
            <pubDate>Fri, 26 Jul 2019 10:34:04 -0700</pubDate>
            
            <guid>https://khare.dev/posts/2019/07/gsoc-2019-phase-2-progress/</guid>
            <description>Phase 2 of GSoC has been completed and here are the updates of the project:
DataFrame library PR#111 and other PRs (102, 103, 104 and 107) have been modified and merged. This means that all issues related to missing data have been fixed! 🎉
In addition to this, two major features have been added - Joins support and JSON I/O.
DataFrame joins SQL-type joins are now possible between DataFrames. There are two APIs for this.</description>
            <content type="html"><![CDATA[

<p>Phase 2 of GSoC has been completed and here are the updates of the project:</p>

<h2 id="dataframe-library">DataFrame library</h2>

<p><a href="https://github.com/PolyMathOrg/DataFrame/pull/111" target="_blank">PR#111</a> and other PRs (102, 103, 104 and 107) have been modified and merged. This means that all issues related to <a href="https://github.com/PolyMathOrg/DataFrame/issues/98" target="_blank">missing data</a> have been fixed! 🎉</p>

<p>In addition to this, two major features have been added - Joins support and JSON I/O.</p>

<h3 id="dataframe-joins">DataFrame joins</h3>

<p>SQL-type joins are now possible between DataFrames. There are two APIs for this. For joins using <code>rowNames</code>, following is used:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">df</span> <span class="nf">innerJoin:</span> <span class="nv">df2</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">outerJoin:</span> <span class="nv">df2</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">leftJoin:</span> <span class="nv">df2</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">rightJoin:</span> <span class="nv">df2</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>And for joins using arbitary columns, the API is:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">df</span> <span class="nf">innerJoin:</span> <span class="nv">df2</span> <span class="nf">onLeft:</span> <span class="s">&#39;LeftCol&#39;</span> <span class="nf">onRight:</span> <span class="s">&#39;RightCol&#39;</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">outerJoin:</span> <span class="nv">df2</span> <span class="nf">onLeft:</span> <span class="s">&#39;LeftCol&#39;</span> <span class="nf">onRight:</span> <span class="s">&#39;RightCol&#39;</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">leftJoin:</span> <span class="nv">df2</span> <span class="nf">onLeft:</span> <span class="s">&#39;LeftCol&#39;</span> <span class="nf">onRight:</span> <span class="s">&#39;RightCol&#39;</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">rightJoin:</span> <span class="nv">df2</span> <span class="nf">onLeft:</span> <span class="s">&#39;LeftCol&#39;</span> <span class="nf">onRight:</span> <span class="s">&#39;RightCol&#39;</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>Joining using rowNames (first API) is faster than using arbitary columns.</p>

<h3 id="json-i-o">JSON I/O</h3>

<p>The library can perform can read or write DataFrames from/to <code>.json</code> files. Under the hood, it uses the <a href="https://github.com/svenvc/NeoJSON" target="_blank">NeoJSON library</a> to read/write json, and then converts it to DataFrames.</p>

<p>There are five different formats of JSON to which read/write is supported:</p>

<ol>
<li><code>columns</code> in which the format is of <code>{column:{rowName:data, ...}, ...}</code></li>
<li><code>index</code> which has the format <code>{rowName:{column:data, ...}, ...}</code></li>
<li><code>values</code> which is an array of arrays (rows)</li>
<li><code>split</code> in which the format is <code>{columns:[columnNames], index:[rowNames], data:[data]}</code></li>
<li><code>records</code> which is an array of rows <code>[{col1: data1, ...}, ...]</code></li>
</ol>

<p>Out of these, <code>split</code> is the fastest format to read/write into, since it stores DataFrame similar to it&rsquo;s internal representation.</p>

<p>There are three ways to write to JSON:</p>

<pre><code>df writeTo: aFileRef using: (DataFrameJsonWriter new).
</code></pre>

<pre><code>DataFrameJsonWriter new write: df to: aFileRef.
</code></pre>

<pre><code>DataFrameJsonWriter new writeAsString: df.
</code></pre>

<p>Similarly, you can read JSON to DataFrame object as:</p>

<pre><code>df := DataFrame readFrom: aFileRef using: (DataFrameJsonReader new).
</code></pre>

<pre><code>df := DataFrameJsonReader new readFrom: aFileRef.
</code></pre>
]]></content>
        </item>
        
        <item>
            <title>GSoC 2019 Phase 1 progress</title>
            <link>https://khare.dev/posts/2019/06/gsoc-2019-phase-1-progress/</link>
            <pubDate>Mon, 24 Jun 2019 19:50:21 +0530</pubDate>
            
            <guid>https://khare.dev/posts/2019/06/gsoc-2019-phase-1-progress/</guid>
            <description>Today (June 24th) marks the completion of phase 1 of GSoC. Here is the summary of what I have been working on for past few weeks:
PolyMath library I spent most of my community bonding period as well as first week of phase 1 on PolyMath library, exploring codebase, fixing bugs and adding features. My most favorite part was adding t-SNE implementation! Here is a peek at a couple of visualizations:</description>
            <content type="html"><![CDATA[

<p>Today (June 24th) marks the completion of phase 1 of GSoC. Here is the summary of what I have been working on for past few weeks:</p>

<h2 id="polymath-library">PolyMath library</h2>

<p>I spent most of my community bonding period as well as first week of phase 1 on PolyMath library, exploring codebase, fixing bugs and adding features. My most favorite part was adding t-SNE implementation! Here is a peek at a couple of visualizations:</p>

<div class="wrapper">
<video loop controls="controls" class="videos" autoplay="autoplay">
  <source src="/videos/tSneDemo.mp4" type="video/mp4">
  Your browser doesnt support playing videos.
</video>
</div>

<p>The visualization is made possible using Roassal3, be sure to check it out at <a href="https://github.com/ObjectProfile/Roassal3" target="_blank">https://github.com/ObjectProfile/Roassal3</a>.</p>

<p>Here is the list of issues I created:</p>

<ol>
<li><a href="https://github.com/PolyMathOrg/PolyMath/issues/115" target="_blank">Math-TSNE is incomplete</a></li>
<li><a href="https://github.com/PolyMathOrg/PolyMath/issues/122" target="_blank">PMVector &gt; &lt; operators modify in-place</a></li>
<li><a href="https://github.com/PolyMathOrg/PolyMath/issues/125" target="_blank">PMVector sum is extremely slow</a></li>
<li><a href="https://github.com/PolyMathOrg/PolyMath/issues/124" target="_blank">PMStandardScalar fails when scale = 0</a></li>
</ol>

<p>And here is the list of PRs that solved them:</p>

<ol>
<li><a href="https://github.com/PolyMathOrg/PolyMath/pull/117" target="_blank">Implementing the t-SNE algorithm</a></li>
<li><a href="https://github.com/PolyMathOrg/PolyMath/pull/123" target="_blank">Fix PMVector comparison operators</a></li>
<li><a href="https://github.com/PolyMathOrg/PolyMath/pull/126" target="_blank">Removed PMVector sum for speedup</a></li>
<li><a href="https://github.com/PolyMathOrg/PolyMath/pull/108" target="_blank">Removed == method from PMVector</a></li>
<li><a href="https://github.com/PolyMathOrg/PolyMath/pull/131" target="_blank">Refactored PMTSNE to include steps</a></li>
<li><a href="https://github.com/PolyMathOrg/PolyMath/pull/133" target="_blank">Added vizualization examples for PMTSNE</a></li>
<li><a href="https://github.com/PolyMathOrg/PolyMath/pull/128" target="_blank">Fixed ZeroDivideError in PMStandardizationScaler</a></li>
</ol>

<h2 id="dataframe-library">DataFrame library</h2>

<p>The main focus for phase 1 was getting the library work with missing data - initializing with missing values, reading files, and providing methods to fill the data.</p>

<p>Here is the list of issues created by me:</p>

<ol>
<li><a href="https://github.com/PolyMathOrg/DataFrame/issues/98" target="_blank">Handling missing data</a> - collection of all issues related to missing data</li>
<li><a href="https://github.com/PolyMathOrg/DataFrame/issues/91" target="_blank"><code>DataFrame select:</code> fails when no rows are selected</a></li>
<li><a href="https://github.com/PolyMathOrg/DataFrame/issues/96" target="_blank">DataSeries does not support boolean operators with scalars</a></li>
<li><a href="https://github.com/PolyMathOrg/DataFrame/issues/97" target="_blank">Add JSON read/write support</a></li>
<li><a href="https://github.com/PolyMathOrg/DataFrame/issues/105" target="_blank">DataFrameInternal - Using OrderedCollection over Array2D</a></li>
<li><a href="https://github.com/PolyMathOrg/DataFrame/issues/106" target="_blank">DataFrame addRow does not consider key order</a></li>
</ol>

<p>PRs which solve some of these issues:</p>

<ol>
<li><a href="https://github.com/PolyMathOrg/DataFrame/pull/99" target="_blank">Boolean operators for DataSeries</a></li>
<li><a href="https://github.com/PolyMathOrg/DataFrame/pull/100" target="_blank">Added support for DataFrame init with missing values</a></li>
<li><a href="https://github.com/PolyMathOrg/DataFrame/pull/102" target="_blank">Added ability to remove nil from DataFrame and Series</a></li>
<li><a href="https://github.com/PolyMathOrg/DataFrame/pull/103" target="_blank">Added DataSeries fillNilsWith method</a></li>
<li><a href="https://github.com/PolyMathOrg/DataFrame/pull/104" target="_blank">Added method to convert missing values from files</a></li>
<li><a href="https://github.com/PolyMathOrg/DataFrame/pull/107" target="_blank">DataFrameTypeDetector now works with nil</a></li>
</ol>

<h3 id="next-steps">Next steps</h3>

<p>Some of the features planned for phase 2 include <a href="https://github.com/PolyMathOrg/DataFrame/issues/58" target="_blank">implenting joins</a>, <a href="https://github.com/PolyMathOrg/DataFrame/issues/97" target="_blank">adding json support</a> and a new <code>DataSet</code> library. I will be creating a detailed post after the first evaluation (first week of July). Be sure to star and follow <a href="https://github.com/PolyMathOrg/DataFrame/" target="_blank">DataFrame</a> and <a href="https://github.com/PolyMathOrg/PolyMath" target="_blank">PolyMath</a> on Github for updates on the progress! :)</p>
]]></content>
        </item>
        
        <item>
            <title>GSoC Community Bonding period: Summary</title>
            <link>https://khare.dev/posts/2019/05/gsoc-community-bonding-period-summary/</link>
            <pubDate>Wed, 29 May 2019 18:12:53 +0530</pubDate>
            
            <guid>https://khare.dev/posts/2019/05/gsoc-community-bonding-period-summary/</guid>
            <description>The community bonding period ended on May 27th, here is the summary of the work I did till that date. I was suggested to contribute to PolyMath, the parent library of DataFrame, which is used for scientific computing. Here are the issues solved:
1. t-SNE implementation in Pharo t-SNE is a visualization algorithm, through which you can judge your input dataset, reveal it&amp;rsquo;s clusters, etc. The algorithm implemented was incomplete, as mentioned in Issue#115.</description>
            <content type="html"><![CDATA[

<p>The community bonding period ended on May 27th, here is the summary of the work I did till that date. I was suggested to contribute to PolyMath, the parent library of DataFrame, which is used for scientific computing. Here are the issues solved:</p>

<h4 id="1-t-sne-implementation-in-pharo">1. t-SNE implementation in Pharo</h4>

<p>t-SNE is a visualization algorithm, through which you can judge your input dataset, reveal it&rsquo;s clusters, etc. The algorithm implemented was incomplete, as mentioned in <a href="https://github.com/PolyMathOrg/PolyMath/issues/115" target="_blank">Issue#115</a>. I worked torwards completing the algorithm, having a running version in the commit <a href="https://github.com/PolyMathOrg/PolyMath/commit/7f3a8121300b90b97956931d7e0ac4294001a66e" target="_blank">7f3a812</a>. We started using Github Boards for tracking the progress, which can be found <a href="https://github.com/PolyMathOrg/PolyMath/projects/2" target="_blank">here</a>. Overall, I spent about 2 weeks learning the details and implementing the algorithm. I&rsquo;ll be creating a seperate post detailing the algorithm soon.</p>

<h4 id="2-speeding-up-pmvector-sum">2. Speeding up PMVector sum</h4>

<p>While profiling t-SNE, I discovered that current implementation of PMVector <code>sum</code> was very slow. <code>sum</code> is used to calculate the sum of PMVector, and is also used in PMMatrix to calculate sums of rows and columns. You can read more about the issue here: <a href="https://github.com/PolyMathOrg/PolyMath/issues/125" target="_blank">Issue#125</a>. It was easily fixed by relying on parent <code>Collection</code>&rsquo;s <code>sum</code>, which increased speed quite a lot.</p>

<h4 id="3-pmvector-comparision-operators">3. PMVector comparision operators</h4>

<p>PMVector had a redundant and perhaps, wrong operator <code>==</code>, which was removed (<a href="https://github.com/PolyMathOrg/PolyMath/issues/90" target="_blank">Issue#90</a>, <a href="https://github.com/PolyMathOrg/PolyMath/pull/108" target="_blank">PR#108</a>). Also, operators <code>&gt;</code> and <code>&lt;</code> modified the source vector in-place, causing unintuitive behavior while working with PMMatrix: <a href="https://github.com/PolyMathOrg/PolyMath/issues/122" target="_blank">Issue#122</a>, <a href="https://github.com/PolyMathOrg/PolyMath/pull/123" target="_blank">PR#123</a>.</p>

<p><br>
First two weeks were mostly spent on learning about PolyMath and it&rsquo;s classes, integrating Roassal and DataFrame with PolyMath, and exploring codebase for DataFrame. Overall, it has been productive 3 weeks!</p>
]]></content>
        </item>
        
        <item>
            <title>GSoC Community Bonding period: Exploring DataFrame, PolyMath and Roassal</title>
            <link>https://khare.dev/posts/2019/05/gsoc-community-bonding-period-exploring-dataframe-polymath-and-roassal/</link>
            <pubDate>Mon, 20 May 2019 18:51:27 +0530</pubDate>
            
            <guid>https://khare.dev/posts/2019/05/gsoc-community-bonding-period-exploring-dataframe-polymath-and-roassal/</guid>
            <description>I spent the last 2 weeks exploring PolyMath, DataFrame and Roassal. These three libraries were developed independently, and solve different goals: PolyMath for scientific computing, DataFrame for data analysis, and Roassal for visualization. However, the work cohesively, due to the class structure of these libraries.
To demonstrate this, here is a piece of code utilising all three libraries:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82  &amp;#34;Iris plotting in Roassal&amp;#34; reader := DataFrameCsvReader new.</description>
            <content type="html"><![CDATA[<p>I spent the last 2 weeks exploring PolyMath, DataFrame and Roassal. These three libraries were developed independently, and solve different goals: PolyMath for scientific computing, DataFrame for data analysis, and Roassal for visualization. However, the work cohesively, due to the class structure of these libraries.</p>

<p>To demonstrate this, here is a piece of code utilising all three libraries:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="c">&#34;Iris plotting in Roassal&#34;</span>
<span class="nv">reader</span> <span class="o">:=</span> <span class="nc">DataFrameCsvReader</span> <span class="nb">new</span><span class="p">.</span>
<span class="nv">fileRef</span> <span class="o">:=</span>  <span class="s">&#39;iris.csv&#39;</span> <span class="nf">asFileReference</span><span class="p">.</span>
<span class="nv">df</span> <span class="o">:=</span> <span class="nc">DataFrame</span> <span class="nf">readFrom:</span> <span class="nv">fileRef</span> <span class="nf">using:</span> <span class="nv">reader</span><span class="p">.</span>

<span class="nv">df_x</span> <span class="o">:=</span> <span class="nv">df</span> <span class="nf">columns:</span> <span class="ss">#(</span><span class="s">&#39;Sepal length&#39;</span> <span class="s">&#39;Sepal width&#39;</span> <span class="s">&#39;Petal length&#39;</span> <span class="s">&#39;Petal width&#39;</span><span class="ss">)</span><span class="p">.</span>

<span class="nv">dataServer</span> <span class="o">:=</span> <span class="nc">PMMemoryBasedDataServer</span> <span class="nb">new</span><span class="p">.</span>
<span class="nv">dataServer</span> <span class="nf">data:</span> <span class="nv">df_x</span><span class="p">.</span>

<span class="nv">finder</span> <span class="o">:=</span> <span class="nc">PMClusterFinder</span> <span class="nf">new:</span> <span class="m">3</span> <span class="nf">server:</span> <span class="nv">dataServer</span> <span class="nf">type:</span> <span class="nc">PMEuclideanCluster</span><span class="p">.</span>
<span class="nv">finder</span> <span class="nf">minimumRelativeClusterSize:</span> <span class="m">0.01</span><span class="p">.</span>
<span class="nv">clusters</span> <span class="o">:=</span> <span class="nv">finder</span> <span class="nf">evaluate</span><span class="p">.</span>

<span class="nv">y</span> <span class="o">:=</span> <span class="nc">DataSeries</span> <span class="nb">new</span> <span class="nf">name:</span> <span class="s">&#39;Output&#39;</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">withIndexDo:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">:</span><span class="nv">index</span> <span class="o">|</span>
	<span class="nv">y</span> <span class="nf">add:</span> <span class="nv">index</span><span class="nf">-&gt;</span>(<span class="nv">finder</span> <span class="nf">indexOfNearestCluster:</span> ({<span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;Sepal length&#39;</span><span class="p">.</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;Sepal width&#39;</span><span class="p">.</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;Petal length&#39;</span><span class="p">.</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;Petal width&#39;</span>} <span class="nf">asPMVector</span>))<span class="p">.</span>
	]<span class="p">.</span>

<span class="nv">df</span> <span class="nf">addColumn:</span> <span class="nv">y</span><span class="p">.</span>

<span class="nv">m</span> <span class="o">:=</span> <span class="nc">PMMatrix</span> <span class="nf">rows:</span> <span class="nv">df_x</span><span class="p">.</span>
<span class="nv">m</span> <span class="o">:=</span> (<span class="nc">PMStandardizationScaler</span> <span class="nb">new</span>) <span class="nf">fitAndTransform:</span> <span class="nv">m</span><span class="p">.</span>
<span class="nv">pca</span> <span class="o">:=</span> <span class="nc">PMPrincipalComponentAnalyserJacobiTransformation</span> <span class="nb">new</span> <span class="nf">componentsNumber:</span> <span class="m">2</span><span class="p">.</span>
<span class="nv">pca</span> <span class="nf">fit:</span> <span class="nv">m</span><span class="p">.</span>
<span class="nv">reduced</span> <span class="o">:=</span> <span class="nv">pca</span> <span class="nf">transform:</span> <span class="nv">m</span><span class="p">.</span>

<span class="nv">transformOutput</span> <span class="o">:=</span> <span class="nc">Dictionary</span> <span class="nf">newFrom:</span> {
	<span class="m">3</span><span class="nf">-&gt;</span><span class="s">&#39;Iris-setosa&#39;</span> <span class="p">.</span>
	<span class="m">2</span><span class="nf">-&gt;</span><span class="s">&#39;Iris-versicolor&#39;</span> <span class="p">.</span>
	<span class="m">1</span><span class="nf">-&gt;</span><span class="s">&#39;Iris-virginica&#39;</span> <span class="p">.</span>
}.
<span class="nv">df</span> <span class="nf">column:</span> <span class="s">&#39;Output&#39;</span> <span class="nf">transform:</span> [ <span class="o">:</span><span class="nv">column</span> <span class="o">|</span>
	<span class="nv">column</span> <span class="nf">collect:</span> [ <span class="o">:</span><span class="nv">number</span> <span class="o">|</span>
		<span class="nv">transformOutput</span> <span class="nf">at:</span> <span class="nv">number</span><span class="p">.</span>
	]<span class="p">.</span>
]<span class="p">.</span>

<span class="nv">df</span> <span class="nf">addColumn:</span> (<span class="nv">reduced</span> <span class="nf">atColumn:</span> <span class="m">1</span>) <span class="nf">named:</span> <span class="s">&#39;PCA x&#39;</span><span class="p">.</span>
<span class="nv">df</span> <span class="nf">addColumn:</span> (<span class="nv">reduced</span> <span class="nf">atColumn:</span> <span class="m">2</span>) <span class="nf">named:</span> <span class="s">&#39;PCA y&#39;</span><span class="p">.</span>

<span class="nv">b</span> <span class="o">:=</span> <span class="nc">RTGrapher</span> <span class="nb">new</span><span class="p">.</span>

<span class="nv">ds_setosa</span> <span class="o">:=</span> <span class="nc">RTData</span> <span class="nb">new</span><span class="p">.</span>
<span class="nv">ds_setosa</span> <span class="nf">label:</span> <span class="s">&#39;Iris setosa&#39;</span><span class="p">.</span>
<span class="nv">ds_setosa</span> <span class="nf">dotShape</span> <span class="nf">circle</span> <span class="nf">color:</span> <span class="nc">Color</span> <span class="nf">red</span> <span class="nf">trans</span><span class="p">.</span>
<span class="nv">ds_setosa</span> <span class="nf">points:</span> (<span class="nv">df</span> <span class="nf">select:</span> [<span class="o">:</span><span class="nv">row</span> <span class="o">|</span> ((<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>) <span class="nf">=</span> <span class="s">&#39;Iris-setosa&#39;</span>) <span class="nf">&amp;</span> ((<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>) <span class="nf">=</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Output</span>))])<span class="p">.</span>
<span class="nv">ds_setosa</span> <span class="nf">interaction</span> <span class="nf">popupText:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="s">&#39;Actual: &#39;</span><span class="nf">,</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>) <span class="nf">asString,</span> <span class="s">&#39;. Predicted: &#39;</span><span class="nf">,</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Output</span>) <span class="nf">asString</span> ]<span class="p">.</span>
<span class="nv">ds_setosa</span> <span class="nf">x:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;PCA x&#39;</span> ]<span class="p">.</span>
<span class="nv">ds_setosa</span> <span class="nf">y:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;PCA y&#39;</span> ]<span class="p">.</span>
<span class="nv">b</span> <span class="nf">add:</span> <span class="nv">ds_setosa</span><span class="p">.</span>


<span class="nv">ds_versicolor</span> <span class="o">:=</span> <span class="nc">RTData</span> <span class="nb">new</span><span class="p">.</span>
<span class="nv">ds_versicolor</span> <span class="nf">label:</span> <span class="s">&#39;Iris versicolor&#39;</span><span class="p">.</span>
<span class="nv">ds_versicolor</span> <span class="nf">dotShape</span> <span class="nf">circle</span> <span class="nf">color:</span> <span class="nc">Color</span> <span class="nf">blue</span> <span class="nf">trans</span><span class="p">.</span>
<span class="nv">ds_versicolor</span> <span class="nf">points:</span> (<span class="nv">df</span> <span class="nf">select:</span> [<span class="o">:</span><span class="nv">row</span> <span class="o">|</span> ((<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>) <span class="nf">=</span> <span class="s">&#39;Iris-versicolor&#39;</span>) <span class="nf">&amp;</span> ((<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>) <span class="nf">=</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Output</span>))])<span class="p">.</span>
<span class="nv">ds_versicolor</span> <span class="nf">interaction</span> <span class="nf">popupText:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="s">&#39;Actual: &#39;</span><span class="nf">,</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>) <span class="nf">asString,</span> <span class="s">&#39;. Predicted: &#39;</span><span class="nf">,</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Output</span>) <span class="nf">asString</span> ]<span class="p">.</span>
<span class="nv">ds_versicolor</span> <span class="nf">x:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;PCA x&#39;</span> ]<span class="p">.</span>
<span class="nv">ds_versicolor</span> <span class="nf">y:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;PCA y&#39;</span> ]<span class="p">.</span>
<span class="nv">b</span> <span class="nf">add:</span> <span class="nv">ds_versicolor</span><span class="p">.</span>


<span class="nv">ds_virginica</span> <span class="o">:=</span> <span class="nc">RTData</span> <span class="nb">new</span><span class="p">.</span>
<span class="nv">ds_virginica</span> <span class="nf">label:</span> <span class="s">&#39;Iris virginica&#39;</span><span class="p">.</span>
<span class="nv">ds_virginica</span> <span class="nf">dotShape</span> <span class="nf">circle</span> <span class="nf">color:</span> <span class="nc">Color</span> <span class="nf">green</span> <span class="nf">trans</span><span class="p">.</span>
<span class="nv">ds_virginica</span> <span class="nf">points:</span> (<span class="nv">df</span> <span class="nf">select:</span> [<span class="o">:</span><span class="nv">row</span> <span class="o">|</span> ((<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>)) <span class="nf">=</span> <span class="s">&#39;Iris-virginica&#39;</span> <span class="nf">&amp;</span> ((<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>) <span class="nf">=</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Output</span>))])<span class="p">.</span>
<span class="nv">ds_virginica</span> <span class="nf">interaction</span> <span class="nf">popupText:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="s">&#39;Actual: &#39;</span><span class="nf">,</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>) <span class="nf">asString,</span> <span class="s">&#39;. Predicted: &#39;</span><span class="nf">,</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Output</span>) <span class="nf">asString</span> ]<span class="p">.</span>
<span class="nv">ds_virginica</span> <span class="nf">x:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;PCA x&#39;</span> ]<span class="p">.</span>
<span class="nv">ds_virginica</span> <span class="nf">y:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;PCA y&#39;</span> ]<span class="p">.</span>
<span class="nv">b</span> <span class="nf">add:</span> <span class="nv">ds_virginica</span><span class="p">.</span>

<span class="nv">ds_misclassified</span> <span class="o">:=</span> <span class="nc">RTData</span> <span class="nb">new</span><span class="p">.</span>
<span class="nv">ds_misclassified</span> <span class="nf">label:</span> <span class="s">&#39;Misclassified&#39;</span><span class="p">.</span>
<span class="nv">ds_misclassified</span> <span class="nf">dotShape</span> <span class="nf">circle</span> <span class="nf">color:</span> <span class="nc">Color</span> <span class="nf">black</span> <span class="nf">trans</span><span class="p">.</span>
<span class="nv">ds_misclassified</span> <span class="nf">points:</span> (<span class="nv">df</span> <span class="nf">select:</span> [<span class="o">:</span><span class="nv">row</span> <span class="o">|</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>) <span class="nf">~=</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Output</span>)])<span class="p">.</span>
<span class="nv">ds_misclassified</span> <span class="nf">interaction</span> <span class="nf">popupText:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="s">&#39;Actual: &#39;</span><span class="nf">,</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Type</span>) <span class="nf">asString,</span> <span class="s">&#39;. Predicted: &#39;</span><span class="nf">,</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Output</span>) <span class="nf">asString</span> ]<span class="p">.</span>
<span class="nv">ds_misclassified</span> <span class="nf">x:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;PCA x&#39;</span> ]<span class="p">.</span>
<span class="nv">ds_misclassified</span> <span class="nf">y:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> <span class="nv">row</span> <span class="nf">at:</span> <span class="s">&#39;PCA y&#39;</span> ]<span class="p">.</span>
<span class="nv">b</span> <span class="nf">add:</span> <span class="nv">ds_misclassified</span><span class="p">.</span>

<span class="nv">b</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>Here is it exported from Roassal as HTML (go on - hover over the points!):
<div style="position:relative;padding-top:100%;">
<iframe src="/pharo_graphs/iris0.html" style="border:none;position:absolute;top:0;left:0;width:100%;height:100%;"></iframe>
</div>
Note: I have clustered first, then applied PCA. Exercise for the reader - try the opposite!</p>

<p>In the last week, I begun implementing t-SNE for PolyMath. It is exciting to dissect the paper, working through the nitty-gritty details and translating them into code. By the end of next week, it will be the first paper I have implemented! I also am writing an accompanying post, which will explain the math behind the algorithm - it is one of the reason that this week&rsquo;s post is small!</p>

<p>You can track the progress of the implementation at this link: <a href="https://github.com/PolyMathOrg/PolyMath/projects/2" target="_blank">t-SNE project board</a>.</p>
]]></content>
        </item>
        
        <item>
            <title>GSoC 2019: Extending DataFrame library for Pharo Consortium</title>
            <link>https://khare.dev/posts/2019/05/gsoc-2019-extending-dataframe-library-for-pharo-consortium/</link>
            <pubDate>Mon, 13 May 2019 17:41:10 +0530</pubDate>
            
            <guid>https://khare.dev/posts/2019/05/gsoc-2019-extending-dataframe-library-for-pharo-consortium/</guid>
            <description>I have been accepted in Google Summer of Code by Pharo for the dataframe library and during this summer, various features and documentation will be added to the library. The mentors for this project are Oleksandr Zaitsev, Serge Stinckwich and Konrad Hinsen.
About Pharo Pharo is an easy-to-learn, pure object oriented programming language based on smalltalk. Pharo comes with it&amp;rsquo;s integrated IDE, which makes browsing code, documents and examples easier, as well as it&amp;rsquo;s inspector and inbuilt debugging tool allows you for faster development.</description>
            <content type="html"><![CDATA[

<p>I have been <a href="https://summerofcode.withgoogle.com/projects/#4954280035876864" target="_blank">accepted</a> in <a href="https://summerofcode.withgoogle.com/" target="_blank">Google Summer of Code</a> by Pharo for the dataframe library and during this summer, various features and documentation will be added to the library. The mentors for this project are <a href="https://www.linkedin.com/in/-oleks" target="_blank">Oleksandr Zaitsev</a>, <a href="https://www.doesnotunderstand.org/" target="_blank">Serge Stinckwich</a> and <a href="https://khinsen.net/" target="_blank">Konrad Hinsen</a>.</p>

<h2 id="about-pharo">About Pharo</h2>

<p><a href="https://pharo.org/" target="_blank">Pharo</a> is an easy-to-learn, pure object oriented programming language based on smalltalk. Pharo comes with it&rsquo;s integrated IDE, which makes browsing code, documents and examples easier, as well as it&rsquo;s inspector and inbuilt debugging tool allows you for faster development. It also has various libraries like Seaside (web-dev), PolyMath (scientific computing), Roassal (Visualization) etc. Overall, it is a great language for learning object oriented programming as well as enterprise applications.</p>

<h2 id="about-dataframe-library">About DataFrame library</h2>

<p>The <a href="https://github.com/PolyMathOrg/DataFrame" target="_blank">dataFrame library</a> was first created as a GSoC 2017 project by Oleksandr, which introduced multiple functionalities making it fit for data analysis. <a href="https://github.com/SquareBracketAssociates/Booklet-DataFrame" target="_blank">The DataFrame Booklet</a> consists of documentation regarding current API. I have used this library as a base for the <a href="/tags/whatsapp-analyzer/">Whatsapp Analyzer project</a>.</p>

<h2 id="the-plan">The Plan</h2>

<h3 id="1-handling-missing-data">1. Handling missing data</h3>

<p>Advanced missing data functionality such as detecting different types of missing data (<code>NA</code>, <code>nill</code>, <code>?</code>), replacing them, reading files with incomplete data etc. will be added. This should also enable detecting column-type functionality of dataframes.</p>

<h3 id="2-joins-between-dataframe">2. Joins between dataframe</h3>

<p>Merging dataframes using joins (left, right, inner) is crucial and will be supported by end of summer</p>

<h3 id="3-json-import-export">3. JSON import/export</h3>

<p>Json is frequently used for transmitting data over the network, and having a json import/export functionality will enable creating and consuming dataset API endpoints over the internet.</p>

<h3 id="4-mathematical-operations">4. Mathematical operations</h3>

<p>Different operations on DataFrame and DataSeries are planned, such as correlation, covariance, cummulative (min, max, product, sum), clip, and different operators such as <code>&gt;</code>, <code>&lt;</code>, <code>&gt;=</code>, <code>&lt;=</code>, <code>mod</code>, <code>pow</code>. This will bring flexiblity to the library.</p>

<h3 id="5-dataset-fetcher">5. Dataset fetcher</h3>

<p>A new Dataset fetcher would also be added to fetch popular datasets such as Iris, Boston, Mnist etc., making it easier to experiment with the library.</p>

<h3 id="6-documentation-and-tests">6. Documentation and tests</h3>

<p>A good amount of documentation would be added ranging from examples of the API to the comments of the messages, along with additions of tests. The aim is to make the library easy to use for the user</p>

<h3 id="detailed-proposal">Detailed proposal</h3>

<p>The detail proposal has been <a href="/misc/Pharo_GSOC_2019_Dataframe.pdf">uploaded here</a>, which has the timelines and messages that will be implemented.</p>

<h2 id="next-steps">Next steps</h2>

<p>The community bonding period is till May 27th, in which I am going to explore the PolyMath library and fix some issues, as well as refine the tasks and schedule for this summer.</p>

<p><br>
You can track the progress of this project by clicking on the tag &ldquo;GSoC progress&rdquo; at below, or view the monthly posts by clicking on &ldquo;GSoC summary&rdquo;.</p>
]]></content>
        </item>
        
        <item>
            <title>Building Whatsapp Analyzer in Pharo: Analysis &amp; Visualization (Part 3)</title>
            <link>https://khare.dev/posts/2019/05/building-whatsapp-analyzer-in-pharo-analysis-visualization-part-3/</link>
            <pubDate>Sat, 11 May 2019 17:55:51 +0530</pubDate>
            
            <guid>https://khare.dev/posts/2019/05/building-whatsapp-analyzer-in-pharo-analysis-visualization-part-3/</guid>
            <description>Till the last post, we parsed a Whatsapp *.txt chat file, and created helper methods for preprocessing. In this one, we will analyze the messages stored in the dataframe obtained from previous parts.
Prerequisites 1. Parsed df object Follow Part 1 to obtain a df object with messages stored in it. A sample chat has been added for convenience.
2. ChatFeatures and ChatAnayzer classes These classes defined in Part 2 will be used in this part.</description>
            <content type="html"><![CDATA[

<p>Till the last post, we parsed a Whatsapp <code>*.txt</code> chat file, and created helper methods for preprocessing. In this one, we will analyze the messages stored in the dataframe obtained from previous parts.</p>

<h2 id="prerequisites">Prerequisites</h2>

<h4 id="1-parsed-df-object">1. Parsed df object</h4>

<p>Follow Part 1 to obtain a <code>df</code> object with messages stored in it. A sample chat has been added for convenience.</p>

<h4 id="2-chatfeatures-and-chatanayzer-classes">2. ChatFeatures and ChatAnayzer classes</h4>

<p>These classes defined in Part 2 will be used in this part. You can also clone the repo mentioned at the end of part 2 to obtain them.</p>

<h2 id="preparation">Preparation</h2>

<p>Let&rsquo;s create the following class, which will be used to analyze the dataframe.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nc">Object</span> <span class="nf">subclass:</span> <span class="ss">#ChatAnalyzer</span>
   <span class="nf">instanceVariableNames:</span> <span class="s">&#39;&#39;</span>
   <span class="nf">classVariableNames:</span> <span class="s">&#39;&#39;</span>
   <span class="nf">package:</span> <span class="s">&#39;WhatsappAnalyzer&#39;</span></code></pre></td></tr></table>
</div>
</div>
<p>We&rsquo;ll also create a class-side method:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getAuthorsFrom:</span> <span class="nv">df</span>
   <span class="c">&#34;Returns an OrderedCollection of authors present in the dataframe&#34;</span>

   <span class="o">^</span> (<span class="nv">df</span> <span class="nf">column:</span> <span class="ss">#Author</span>) <span class="nf">asSet</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<h2 id="analysis">Analysis</h2>

<h3 id="basic-analysis">Basic Analysis</h3>

<p>The total messages exchanged can be easily found out by <code>df size</code>, since we have already removed system messages (eg - <code>ABC changed the group name to XYZ</code>) and handled multi-line messages in part 2. We can infer messages like: <code>This message was deleted</code> as well as Media <code>&lt;Media Omitted&gt;</code> by using <code>reject</code> similar to <code>ChatCleaner removeMessages: from</code>. (Note: The error block is due to a bug in the library, which will be fixed soon)</p>

<p>We will store all results in a <code>Dictionary</code>, which can be later used to turn into a <code>json</code> format using <code>NeoJSON</code>.</p>

<p>Here is a basic method which analyzes basic message exchanges:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getMessageCounts:</span> <span class="nv">df</span>
   <span class="c">&#34;Counts total messages, text messages, media and deleted messages&#34;</span>

   <span class="o">|</span><span class="nv"> messageCounts tempDf </span><span class="o">|</span>
   <span class="nv">messageCounts</span> <span class="o">:=</span> <span class="nc">Dictionary</span> <span class="nb">new</span><span class="p">.</span>
   <span class="nv">messageCounts</span> <span class="nf">add:</span> <span class="s">&#39;Total&#39;</span><span class="nf">-&gt;</span>(<span class="nv">df</span> <span class="nf">size</span>)<span class="p">.</span>
   [
      <span class="nv">tempDf</span> <span class="o">:=</span> <span class="nv">df</span> <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Message</span>) <span class="nf">=</span> <span class="s">&#39;&lt;Media omitted&gt;&#39;</span> ]<span class="p">.</span>
      <span class="nv">messageCounts</span> <span class="nf">add:</span> <span class="s">&#39;Media&#39;</span><span class="nf">-&gt;</span>(<span class="nv">tempDf</span> <span class="nf">size</span>)<span class="p">.</span>
   ]   <span class="nf">ifError:</span> [ 
      <span class="nv">messageCounts</span> <span class="nf">add:</span> <span class="s">&#39;Media&#39;</span><span class="nf">-&gt;</span><span class="m">0</span><span class="p">.</span>
       ]<span class="p">.</span>
   [
      <span class="nv">tempDf</span> <span class="o">:=</span> <span class="nv">df</span> <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Message</span>) <span class="nf">=</span> <span class="s">&#39;This message was deleted&#39;</span> ]<span class="p">.</span>
      <span class="nv">messageCounts</span> <span class="nf">add:</span> <span class="s">&#39;Deleted&#39;</span><span class="nf">-&gt;</span>(<span class="nv">tempDf</span> <span class="nf">size</span>)<span class="p">.</span>
   ]   <span class="nf">ifError:</span> [ 
      <span class="nv">messageCounts</span> <span class="nf">add:</span> <span class="s">&#39;Deleted&#39;</span><span class="nf">-&gt;</span><span class="m">0</span><span class="p">.</span>
       ]<span class="p">.</span>
   <span class="nv">messageCounts</span> <span class="nf">add:</span> <span class="s">&#39;Text&#39;</span><span class="nf">-&gt;</span>((<span class="nv">df</span> <span class="nf">size</span>) <span class="nf">-</span> (<span class="nv">messageCounts</span> <span class="nf">at:</span> <span class="s">&#39;Media&#39;</span>) <span class="nf">-</span> (<span class="nv">messageCounts</span> <span class="nf">at:</span> <span class="s">&#39;Deleted&#39;</span>))<span class="p">.</span>
   <span class="o">^</span> <span class="nv">messageCounts</span></code></pre></td></tr></table>
</div>
</div>
<p>You can use it as:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">messageCounts</span> <span class="o">:=</span> <span class="nc">ChatAnalyzer</span> <span class="nf">getMessageCounts:</span> <span class="nv">df</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>By filtering <code>Author</code> field, we can do same for getting each author&rsquo;s message counts.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getAuthorMessageCounts:</span> <span class="nv">df</span>
   <span class="c">&#34;Generates message counts for each author&#34;</span>

   <span class="o">|</span><span class="nv"> messageCounts authors authorDf </span><span class="o">|</span>
   <span class="nv">messageCounts</span> <span class="o">:=</span> <span class="nc">Dictionary</span> <span class="nb">new</span><span class="p">.</span>
   <span class="nv">authors</span> <span class="o">:=</span> (<span class="nv">df</span> <span class="nf">column:</span> <span class="ss">#Author</span>) <span class="nf">asSet</span><span class="p">.</span>
   <span class="nv">authors</span> <span class="nf">do:</span> [ <span class="o">:</span><span class="nv">author</span> <span class="o">|</span>
      <span class="nv">authorDf</span> <span class="o">:=</span> <span class="nv">df</span> <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Author</span>) <span class="nf">=</span> <span class="nv">author</span>]<span class="p">.</span>
      <span class="nv">messageCounts</span> <span class="nf">add:</span> <span class="nv">author</span><span class="nf">-&gt;</span>(<span class="bp">self</span> <span class="nf">getMessageCounts:</span> (<span class="nv">authorDf</span>))<span class="p">.</span>
       ]<span class="p">.</span>
   <span class="o">^</span> <span class="nv">messageCounts</span> </code></pre></td></tr></table>
</div>
</div>
<p>You can add it to previous <code>messageCounts</code> as:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">messageCounts</span> <span class="nf">add:</span> <span class="s">&#39;Authors&#39;</span><span class="nf">-&gt;</span>(<span class="nc">ChatAnalyzer</span> <span class="nf">getAuthorMessageCounts:</span> <span class="nv">df</span>)<span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>Now, we can extract metrics such as <code>most active user</code>, <code>average message per user</code> and others. To group them, we create yet another method:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getBasicTextAnalysis:</span> <span class="nv">df</span>
   <span class="c">&#34;Returns a dictionary with following metrics:
</span><span class="c">      Most active user w/count
</span><span class="c">      Most active media user w/count
</span><span class="c">      Messages/user 
</span><span class="c">   &#34;</span>

   <span class="o">|</span><span class="nv"> messageCounts textAnalysis</span><span class="o">|</span>
   <span class="nv">textAnalysis</span> <span class="o">:=</span> <span class="nc">Dictionary</span> <span class="nb">new</span><span class="p">.</span>
   <span class="nv">messageCounts</span> <span class="o">:=</span> <span class="bp">self</span> <span class="nf">getMessageCounts:</span> <span class="nv">df</span><span class="p">.</span>
   <span class="nv">messageCounts</span> <span class="nf">add:</span> <span class="s">&#39;Authors&#39;</span><span class="nf">-&gt;</span>(<span class="bp">self</span> <span class="nf">getAuthorMessageCounts:</span> <span class="nv">df</span>)<span class="p">.</span>
   
   <span class="c">&#34;Calculate most active user&#34;</span>
   <span class="nv">textAnalysis</span> <span class="nf">add:</span> <span class="s">&#39;Most active user&#39;</span><span class="nf">-&gt;</span><span class="s">&#39;&#39;</span><span class="p">.</span>
   <span class="nv">textAnalysis</span> <span class="nf">add:</span> <span class="s">&#39;Most active user messages&#39;</span><span class="nf">-&gt;</span><span class="m">0</span><span class="p">.</span>
   
   <span class="c">&#34;Average messages per user&#34;</span>   
   <span class="nv">textAnalysis</span> <span class="nf">add:</span> <span class="s">&#39;Messages/user&#39;</span><span class="nf">-&gt;</span><span class="m">0</span><span class="p">.</span>  
   (<span class="nv">messageCounts</span> <span class="nf">at:</span> <span class="ss">#Authors</span>) <span class="nf">keysAndValuesDo:</span> [ <span class="o">:</span><span class="nv">key</span> <span class="o">:</span><span class="nv">value</span> <span class="o">|</span>
      ((<span class="nv">textAnalysis</span> <span class="nf">at:</span> <span class="s">&#39;Most active user messages&#39;</span>) <span class="nf">&lt;</span> (<span class="nv">value</span> <span class="nf">at:</span> <span class="s">&#39;Text&#39;</span>)) <span class="nb">ifTrue:</span> [ 
            <span class="nv">textAnalysis</span> <span class="nf">at:</span> <span class="s">&#39;Most active user messages&#39;</span> <span class="nf">put:</span> (<span class="nv">value</span> <span class="nf">at:</span> <span class="s">&#39;Text&#39;</span>)<span class="p">.</span>
            <span class="nv">textAnalysis</span> <span class="nf">at:</span> <span class="s">&#39;Most active user&#39;</span> <span class="nf">put:</span> <span class="nv">key</span><span class="p">.</span>
          ]<span class="p">.</span>
      <span class="nv">textAnalysis</span> <span class="nf">at:</span> <span class="s">&#39;Messages/user&#39;</span> <span class="nf">put:</span> ((<span class="nv">textAnalysis</span> <span class="nf">at:</span> <span class="s">&#39;Messages/user&#39;</span>) <span class="nf">+</span> (<span class="nv">value</span> <span class="nf">at:</span> <span class="s">&#39;Text&#39;</span>))<span class="p">.</span>
       ]<span class="p">.</span>
   
   <span class="nv">textAnalysis</span> <span class="nf">at:</span> <span class="s">&#39;Messages/user&#39;</span> <span class="nf">put:</span> (<span class="nv">textAnalysis</span> <span class="nf">at:</span> <span class="s">&#39;Messages/user&#39;</span>) <span class="nf">asFloat</span> <span class="nf">/</span> ((<span class="nv">messageCounts</span> <span class="nf">at:</span> <span class="ss">#Authors</span>) <span class="nf">size</span>)<span class="p">.</span>
   <span class="nv">messageCounts</span> <span class="nf">add:</span> <span class="s">&#39;Basic Text Analysis&#39;</span><span class="nf">-&gt;</span><span class="nv">messageCounts</span><span class="p">.</span>
   
   <span class="o">^</span> <span class="nv">messageCounts</span></code></pre></td></tr></table>
</div>
</div>
<p>We iterate through summary of each Author&rsquo;s messages using <code>messageCounts at: 'Authors' keysAndValuesDo:</code> and count occurences as needed. For example - Most active user is found out by finding max of <code>Text</code> key for each Author.</p>

<p>Additional fields for this analysis is present on my repo. Here is how the resulting <code>messageCounts</code> will look:</p>

<p><img src="/images/WApart3/BasicTextAnalysis.png" alt="Basic text analysis" /></p>

<h3 id="emojis">Emojis</h3>

<h4 id="1-most-frequently-used-emojis">1. Most frequently used emojis</h4>

<p>In the last part, we added <code>ChatCleaner getEmojisFrom:</code> which returned a dataseries with only emojis present. We&rsquo;ll iterate through the series counting the emojis encountered and storing it in <code>emojiCount</code> which is a <code>DataSeries</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getEmojiCountFrom:</span> <span class="nv">emojiDs</span>
   <span class="c">&#34;Returns an DataSeries having an emoji as key and counts as values&#34;</span>

   <span class="o">|</span><span class="nv"> emojiCount </span><span class="o">|</span>
   <span class="nv">emojiCount</span> <span class="o">:=</span> <span class="nc">DataSeries</span> <span class="nb">new</span> <span class="nf">name:</span> <span class="ss">#EmojiCount</span><span class="p">.</span>
   <span class="nv">emojiDs</span> <span class="nf">do:</span> [ <span class="o">:</span><span class="nv">message</span> <span class="o">|</span>
      <span class="nv">message</span> <span class="nf">do:</span> [ <span class="o">:</span><span class="nv">emoji</span> <span class="o">|</span>
         <span class="nv">emojiCount</span> <span class="nf">at:</span> <span class="nv">emoji</span>
            <span class="nf">transform:</span> [ <span class="o">:</span><span class="nv">count</span> <span class="o">|</span> <span class="nv">count</span> <span class="nf">+</span> <span class="m">1</span> ]
            <span class="nf">ifAbsent:</span> [ <span class="nv">emojiCount</span> <span class="nf">add:</span> <span class="nv">emoji</span><span class="nf">-&gt;</span><span class="m">1</span> ]<span class="p">.</span>
         ]
      ]<span class="p">.</span>
   <span class="o">^</span> <span class="nv">emojiCount</span> <span class="nf">sortDescending</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>We can now get this result:
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="hl"><span class="lnt">3
</span></span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">emojiDs</span> <span class="o">:=</span> <span class="nc">ChatCleaner</span> <span class="nf">getEmojisFrom:</span> <span class="nv">df</span><span class="p">.</span>
<span class="nc">ChatAnalyzer</span> <span class="nf">getEmojiCountFrom:</span> <span class="nv">emojiDs</span><span class="p">.</span>
<span class="hl"><span class="nv">a</span> <span class="nf">DataSeries</span>(<span class="sc">$🏻</span><span class="nf">-&gt;</span><span class="m">7</span> <span class="err">$👍</span><span class="nf">-&gt;</span><span class="m">5</span> <span class="err">$♂</span><span class="nf">-&gt;</span><span class="m">3</span> <span class="err">$</span><span class="nf">‍</span><span class="err">-&gt;</span><span class="m">3</span> <span class="err">$🙋</span><span class="nf">-&gt;</span><span class="m">3</span> <span class="err">$🙂</span><span class="nf">-&gt;</span><span class="m">2</span> <span class="err">$😅</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$🤷</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$🤔</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$🎉</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$😂</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$🤨</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$😁</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$😓</span><span class="nf">-&gt;</span><span class="m">1</span>)</span></code></pre></td></tr></table>
</div>
</div>
Whatsapp seems to have the format <code>&lt;emoji&gt;&lt;color&gt;&lt;gender&gt;</code> which is why color ranks highest (default is yellow), as well as male also is high (since default is female in some emojis). The 4th element in the series has unicode <code>8205</code> which occurs between emoji and <code>♂</code>. We can ignore them by adding an ignore-set.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">ignoreEmoji</span> <span class="o">:=</span> <span class="nc">Set</span> <span class="nf">withAll:</span> <span class="ss">#(</span><span class="m">127995</span> <span class="m">9794</span> <span class="m">8205</span> <span class="m">2640</span><span class="ss">)</span><span class="p">.</span>
<span class="c">&#34;and add following in main body before line 8&#34;</span>
(<span class="nv">ignoreEmoji</span> <span class="nf">includes:</span> (<span class="nv">emoji</span> <span class="nf">asUnicode</span>)) <span class="nb">ifFalse:</span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"> <span class="nv">a</span> <span class="nf">DataSeries</span>(<span class="sc">$👍</span><span class="nf">-&gt;</span><span class="m">5</span> <span class="err">$🙋</span><span class="nf">-&gt;</span><span class="m">3</span> <span class="err">$🙂</span><span class="nf">-&gt;</span><span class="m">2</span> <span class="err">$😅</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$🤷</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$🤔</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$🎉</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$😂</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$🤨</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$😁</span><span class="nf">-&gt;</span><span class="m">1</span> <span class="err">$😓</span><span class="nf">-&gt;</span><span class="m">1</span>)</code></pre></td></tr></table>
</div>
</div>
<h4 id="2-emojis-used-per-person">2. Emojis used per person</h4>

<p>We can extend our previous method by filtering each author and counting emojis used by that author.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getMemberEmojiCountFrom:</span> <span class="nv">df</span>
   <span class="c">&#34;Returns a dictionory of DataSeries having emoji count of member&#34;</span>

   <span class="o">|</span><span class="nv"> emojiPerPerson authors authorDf authorEmojiCount </span><span class="o">|</span>
   <span class="nv">emojiPerPerson</span> <span class="o">:=</span> <span class="nc">Dictionary</span> <span class="nb">new</span><span class="p">.</span>
   <span class="nv">authors</span> <span class="o">:=</span> (<span class="nv">df</span> <span class="nf">column:</span> <span class="ss">#Author</span>) <span class="nf">uniqueValues</span><span class="p">.</span>
   <span class="nv">authors</span> <span class="nf">do:</span> [ <span class="o">:</span><span class="nv">author</span> <span class="o">|</span>
      <span class="nv">authorDf</span> <span class="o">:=</span> <span class="nv">df</span> <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Author</span>) <span class="nf">=</span> <span class="nv">author</span>]<span class="p">.</span>
      <span class="nv">authorEmojiCount</span> <span class="o">:=</span> <span class="bp">self</span> <span class="nf">getEmojiCountFrom:</span> (<span class="nc">ChatCleaner</span> <span class="nf">getEmojisFrom:</span> <span class="nv">authorDf</span>)<span class="p">.</span>
      (<span class="nv">authorEmojiCount</span> <span class="nf">isEmpty</span>) <span class="nb">ifFalse:</span> [
         <span class="nv">emojiPerPerson</span> <span class="nf">add:</span> <span class="nv">author</span><span class="nf">-&gt;</span><span class="nv">authorEmojiCount</span><span class="p">.</span>
         ]
       ]<span class="p">.</span>
   <span class="o">^</span> <span class="nv">emojiPerPerson</span></code></pre></td></tr></table>
</div>
</div>
<p>We filter an author&rsquo;s posts in line 8, get emoji count of resulting dataframe, and add it into <code>emojiPerPerson</code>. Here is how the output looks like for the sample chat provided in part 1:
<img src="/images/WApart3/EmojisPerAuthor.png" alt="Emojis Per Author" />
Inspector is unable to render some unicode characters, however you can export it to csv and see the output.</p>

<h3 id="frequently-used-phrases">Frequently used phrases</h3>

<p>To see phrases commonly used in a chat, we can count ngrams across all messages. This is done by iterating through the messages and adding ngrams in a dictionary with key as ngram and count as value, similar to counting of emojis.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getNgramCount:</span> <span class="nv">ngram</span> <span class="nf">columnName:</span> <span class="nv">columnName</span>
   <span class="c">&#34;Returns a DataSeries with ngram count.&#34;</span>

   <span class="o">|</span><span class="nv"> nGramCount </span><span class="o">|</span>
   <span class="nv">nGramCount</span> <span class="o">:=</span> <span class="nc">DataSeries</span> <span class="nb">new</span> <span class="nf">name:</span> <span class="nv">columnName</span><span class="p">.</span>

   <span class="nv">ngrams</span> <span class="nf">do:</span> [ <span class="o">:</span><span class="nv">ngramCollection</span> <span class="o">|</span>
     <span class="nv">ngramCollection</span> <span class="nf">do:</span> [ <span class="o">:</span><span class="nv">ngram</span> <span class="o">|</span>
       <span class="nv">nGramCount</span> <span class="nf">at:</span> <span class="nv">ngram</span>
               <span class="nf">transform:</span> [ <span class="o">:</span><span class="nv">count</span> <span class="o">|</span> <span class="nv">count</span> <span class="nf">+</span> <span class="m">1</span> ]
               <span class="nf">ifAbsent:</span> [ <span class="nv">nGramCount</span> <span class="nf">add:</span> <span class="nv">ngram</span><span class="nf">-&gt;</span><span class="m">1</span> ]<span class="p">.</span>
        ]<span class="p">.</span>
      ]<span class="p">.</span>

   <span class="o">^</span> <span class="nv">nGramCount</span> <span class="nf">sortDescending</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>You can run it and see the output using:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">blacklist</span> <span class="o">:=</span> <span class="nc">Set</span> <span class="nf">withAll:</span> <span class="ss">#(</span><span class="s">&#39;This message was deleted&#39;</span> <span class="s">&#39;&lt;Media omitted&gt;&#39;</span><span class="ss">)</span><span class="p">.</span>

<span class="nv">nGramDf</span> <span class="o">:=</span> <span class="nv">df</span> <span class="nf">deepCopy</span><span class="p">.</span>
<span class="nv">nGramDf</span> <span class="o">:=</span> <span class="nc">ChatCleaner</span> <span class="nf">removeMessages:</span> <span class="nv">blacklist</span> <span class="nf">from:</span> <span class="nv">nGramDf</span><span class="p">.</span>
<span class="nv">nGramDf</span> <span class="nf">column:</span> <span class="ss">#Message</span> <span class="nf">put:</span> ((<span class="nc">ChatCleaner</span> <span class="nf">getWordsFrom:</span> <span class="nv">nGramDf</span>) <span class="nf">asArray</span>)<span class="p">.</span>
<span class="nv">nGramDf</span> <span class="nf">column:</span> <span class="ss">#Message</span> <span class="nf">put:</span> ((<span class="nc">ChatCleaner</span> <span class="nf">messagesAsLowercase:</span> <span class="nv">nGramDf</span>) <span class="nf">asArray</span>)<span class="p">.</span>
<span class="nv">nGramDf</span> <span class="nf">column:</span> <span class="ss">#Message</span> <span class="nf">put:</span> ((<span class="nc">ChatCleaner</span> <span class="nf">removeStopwordsFrom:</span> <span class="nv">nGramDf</span>) <span class="nf">asArray</span>)<span class="p">.</span>
<span class="nv">nGramDf</span> <span class="nf">addColumn:</span> (<span class="nc">ChatFeatures</span> <span class="nf">getNgramsFromDataFrame:</span> <span class="nv">nGramDf</span> <span class="nf">withN:</span> <span class="m">3</span>) <span class="nf">named:</span> <span class="ss">#Ngrams</span><span class="p">.</span>

<span class="nv">nGramCount</span> <span class="o">:=</span> <span class="nc">ChatAnalyzer</span> <span class="nf">getNgramCount:</span> (<span class="nv">nGramDf</span> <span class="nf">column:</span><span class="ss">#Ngrams</span>) <span class="nf">columnName:</span> <span class="ss">#NgramCount</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>To summarize, line 2 removes all punctuation and emojis, line 3 converts remaining characters to lowercase, and line 4 strips the messages of the stopwords. Line 5 adds a new column with ngrams which is used to create <code>nGramCount</code>.</p>

<p>Here is how the output looks like:
<img src="/images/WApart3/Ngram.png" alt="Ngram counts" />
The left is with stopwords removed and right is without. Removing stopwords is only effective if the messages are bigger. Also, the ngram methods in <code>ChatCleaner</code> accept ngrams less than n to prevent null strings. It can be modified to having only n-length ngrams by making sure buffer is full before appending to array.</p>

<p>The above method can be applied to per-author by applying filters, as done in basic analysis.</p>

<h3 id="datetime-analysis">DateTime analysis</h3>

<p>Grouping messages by date or time can allow us to find most frequent hour/date of the chat history. In Pharo, you can group objects using the method <code>group: by: aggregateUsing:</code>, such as for finding most frequent date, we can:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">df</span>
   <span class="err">group:</span> <span class="ss">#Message</span>
   <span class="nf">by:</span> <span class="ss">#Date</span>
   <span class="nf">aggregateUsing:</span> <span class="ss">#size</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>It first groups messages by date, and calculates the size of the created group, returning a DataSeries.</p>

<p>As for the most frequent hour, we need to first convert time strings into hour, followed by grouping.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">df</span> <span class="nf">addColumn:</span> ((<span class="nv">df</span> <span class="nf">column:</span> <span class="ss">#Time</span>) <span class="nf">collect:</span> [
      <span class="o">:</span><span class="nv">element</span> <span class="o">|</span>
      <span class="nv">element</span> <span class="nf">asTime</span> <span class="nf">hour</span>
   ]) <span class="nf">named:</span> <span class="ss">#Hour</span><span class="p">.</span>

<span class="nf">df</span>
   <span class="err">group:</span> <span class="ss">#Message</span>
   <span class="nf">by:</span> <span class="ss">#Hour</span>
   <span class="nf">aggregateUsing:</span> <span class="ss">#size</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>We can do the same for each user by filtering Author. Here is how it will look for <code>Member26</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk">(<span class="nv">nGramDf</span> <span class="nf">select:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Author</span>) <span class="nf">=</span> <span class="s">&#39;Member26&#39;</span> ])
   <span class="nf">group:</span> <span class="ss">#Message</span>
   <span class="nf">by:</span> <span class="ss">#Hour</span>
   <span class="nf">aggregateUsing:</span> <span class="ss">#size</span><span class="p">.</span>
<span class="err">&gt;&gt;&gt;</span> <span class="nv">a</span> <span class="nf">DataSeries</span>(<span class="m">9</span><span class="nf">-&gt;</span><span class="m">20</span> <span class="nf">12</span><span class="err">-&gt;</span><span class="m">15</span> <span class="nf">13</span><span class="err">-&gt;</span><span class="m">1</span>)   </code></pre></td></tr></table>
</div>
</div>
<p>This indicates that user is active mostly around 9am and 12pm.</p>

<h2 id="visualization">Visualization</h2>

<p>Following is the analysis of one of my chats. Visualisation is done using Roassal. A good resource to learn it is <a href="http://agilevisualization.com/" target="_blank">http://agilevisualization.com/</a>, and from it&rsquo;s inbuilt browser containing multiple examples. I did not get enough time to learn Roassal completely so my scripts are bit of a hack, that&rsquo;s why I have just linked the output. If you are curious about the source code, or wish to see a detailed part on this, mail me.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text">Totals:
   Letters exchanged: 413,040
   Words exchanged: 80,474
   Media: 285
   Deleted: 132
   Total messages: 20,584
   Duration: 2017-02-10 to 2019-04-13</code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text">Basic text analysis:
   Most active user: Member1
   Most active user messages: 12104
   Most active user percentage: 60.02%
   Most active media user: Member1
   Most active user media: 226
   Average messages/user: 10,083.5</code></pre></td></tr></table>
</div>
</div>
<p>Message distribution by user:
<div class="wrapper">
<video loop controls="controls" class="videos" autoplay="autoplay">
  <source src="/videos/MessageDistribution.mp4" type="video/mp4">
  Your browser doesnt support playing videos.
</video>
</div></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text">Most common emoji: 😂 appeared 2344 times (80% of all emojis).</code></pre></td></tr></table>
</div>
</div>
<p>Top 10 emoji-wheel for member 1:
<div class="wrapper">
<video loop controls="controls" class="videos" autoplay="autoplay">
  <source src="/videos/EmojiWheel.mp4" type="video/mp4">
  Your browser doesnt support playing videos.
</video>
</div>
<em>Note that I could not find Android emoji fonts for MacOS, on which this was recorded.</em> For reference, here are top 10 emojis:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text">😂, 🤣, 🤦, 😛, 😅, 😒, 🤭, 😌, 😏, 🔥</code></pre></td></tr></table>
</div>
</div>
<p>Messages per hour by members:
<img src="/images/WApart3/MessagesPerHour.png" alt="Messages per hour" /></p>

<p>Here is messages exchanged vs month:
<img src="/images/WApart3/MessagesWithTime.png" alt="Messages per month" /></p>

<p><code>NameCloud</code> for n=1:
<img src="/images/WApart3/WordCount.png" alt="Word Count" /></p>

<p><code>NameCloud</code> for n=3:
<img src="/images/WApart3/Trigram.png" alt="Trigram" /></p>

<h2 id="next-steps">Next steps</h2>

<p>With the last 3 tutorials, you&rsquo;ll have tools to analyze your own chat data. You can extend this to other chats such as discord or even mailing list, or add additional insights such as:</p>

<ul>
<li>member which initiates conversations</li>
<li>member&rsquo;s behavioral pattern</li>
<li>trending topics across chats/months</li>
</ul>

<p>and so on.</p>

<p>If you spot any error in this series or can think of additional analysis that can be done, feel free to email me!</p>
]]></content>
        </item>
        
        <item>
            <title>Building Whatsapp Analyzer in Pharo: Preprocessing (Part 2)</title>
            <link>https://khare.dev/posts/2019/04/building-whatsapp-analyzer-in-pharo-preprocessing-part-2/</link>
            <pubDate>Mon, 15 Apr 2019 17:51:08 +0530</pubDate>
            
            <guid>https://khare.dev/posts/2019/04/building-whatsapp-analyzer-in-pharo-preprocessing-part-2/</guid>
            <description>In the last post, we were able to parse a Whatsapp chat *.txt file into a dataframe object. In this one, we will preprocess the dataframe to make it ready for analysis.
Prerequisites 1. WhatsappReader class The WhatsappReader class provides way to parse exported chat into the dataframe. Follow steps of the last part to create such class.
2. A parsed dataframe You must also have a df object containing parsed messages.</description>
            <content type="html"><![CDATA[

<p>In the last post, we were able to parse a Whatsapp chat <code>*.txt</code> file into a dataframe object. In this one, we will preprocess the dataframe to make it ready for analysis.</p>

<h2 id="prerequisites">Prerequisites</h2>

<h4 id="1-whatsappreader-class">1. WhatsappReader class</h4>

<p>The <code>WhatsappReader</code> class provides way to parse exported chat into the dataframe. Follow steps of the last part to create such class.</p>

<h4 id="2-a-parsed-dataframe">2. A parsed dataframe</h4>

<p>You must also have a <code>df</code> object containing parsed messages. You can create one using last code snippet in previous tutorial.</p>

<h2 id="preparation">Preparation</h2>

<p>We will create two classes - <code>ChatCleaner</code> and <code>ChatFeatures</code>.</p>

<p><code>ChatCleaner</code> would contain messages to clean the chat, such as converting to lowercase, removing stopwords etc.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nc">Object</span> <span class="nf">subclass:</span> <span class="ss">#ChatCleaner</span>
  <span class="nf">instanceVariableNames:</span> <span class="s">&#39;&#39;</span>
  <span class="nf">classVariableNames:</span> <span class="s">&#39;&#39;</span>
  <span class="nf">package:</span> <span class="s">&#39;WhatsappAnalyzer&#39;</span></code></pre></td></tr></table>
</div>
</div>
<p><code>ChatFeatures</code> would provide messages to extract features from the chats, such as ngrams.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nc">Object</span> <span class="nf">subclass:</span> <span class="ss">#ChatFeatures</span>
  <span class="nf">instanceVariableNames:</span> <span class="s">&#39;&#39;</span>
  <span class="nf">classVariableNames:</span> <span class="s">&#39;&#39;</span>
  <span class="nf">package:</span> <span class="s">&#39;WhatsappAnalyzer&#39;</span></code></pre></td></tr></table>
</div>
</div>
<h2 id="generating-n-grams">Generating n-grams</h2>

<p>n-grams are sets of <code>n</code> consecutive words/characters extracted from a string. These are generally used by aggregating n-grams and extracting top by count. eg: n-grams (n=2) of the string <code>I am feeling lucky!</code> will be <code>I am</code>, <code>am feeling</code>, <code>feeling lucky</code>. Counting n-grams across corpora is simple yet effective strategy to extract information from given texts.</p>

<p>We can generate n-grams by using <code>an OrderedCollection lastWords</code> to use as a buffer for last n words. We concatenate when buffer is full using <code>join</code> operator before adding it in <code>n-grams</code>. Define the following in the <code>ChatFeatures</code> class-side method.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getNgramsFromLine:</span> <span class="nv">line</span> <span class="nf">withN:</span> <span class="nv">n</span>
  <span class="c">&#34;Returns an OrderedCollection of word ngrams with n = parameter n.&#34;</span>

  <span class="o">|</span><span class="nv"> ngrams lastWords listOfWords </span><span class="o">|</span>
  <span class="nv">ngrams</span> <span class="o">:=</span> <span class="nc">OrderedCollection</span> <span class="nb">new</span><span class="p">.</span>
  <span class="nv">lastWords</span> <span class="o">:=</span> <span class="nc">OrderedCollection</span> <span class="nb">new</span><span class="p">.</span>
  <span class="nv">listOfWords</span> <span class="o">:=</span> <span class="nv">line</span> <span class="nf">splitOn:</span> <span class="nc">Character</span> <span class="nf">space</span><span class="p">.</span>
  
  <span class="nv">listOfWords</span> <span class="nf">do:</span> [ <span class="o">:</span><span class="nv">word</span> <span class="o">|</span>
    (<span class="nv">lastWords</span> <span class="nf">size</span> <span class="nf">&lt;</span> <span class="nv">n</span>) <span class="nb">ifFalse:</span> [
      <span class="nv">ngrams</span> <span class="nf">add:</span> (<span class="s">&#39; &#39;</span> <span class="nf">join:</span> <span class="nv">lastWords</span>)<span class="p">.</span>
      <span class="nv">lastWords</span> <span class="nf">removeFirst</span><span class="p">.</span>
      ]<span class="p">.</span>
      <span class="nv">lastWords</span> <span class="nf">addLast:</span> <span class="nv">word</span><span class="p">.</span>
   ]<span class="p">.</span>
  <span class="nv">ngrams</span> <span class="nf">add:</span> (<span class="s">&#39; &#39;</span> <span class="nf">join:</span> <span class="nv">lastWords</span>)<span class="p">.</span>
  
  <span class="o">^</span> <span class="nv">ngrams</span></code></pre></td></tr></table>
</div>
</div>
<p>In line 12, we remove the first word from <code>lastWords</code>. Since n-grams act as a sliding window, we are emulating a queue in <code>lastWords</code> by adding at end and removing front. We also have added another <code>ngram add</code> at the end, to flush the buffer.</p>

<p>Here is how to use it:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">line</span> <span class="o">:=</span> <span class="s">&#39;A quick brown fox&#39;</span><span class="p">.</span>
<span class="nc">ChatFeatures</span> <span class="nf">getNgramsFromLine:</span> <span class="nv">line</span> <span class="nf">withN:</span> <span class="m">2</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">an</span> <span class="nf">OrderedCollection</span>(<span class="s">&#39;A quick&#39;</span> <span class="err">&#39;</span><span class="nf">quick</span> <span class="nf">brown</span><span class="err">&#39;</span> <span class="err">&#39;</span><span class="nf">brown</span> <span class="nf">fox</span><span class="err">&#39;</span>)</code></pre></td></tr></table>
</div>
</div>
<p>For character n-grams, we pass a <code>String</code> object instead of <code>listOfWords</code>, and join <code>lastWords</code> (or here, <code>lastChars</code>) using an empty string <code>''</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getCharNgramsFromLine:</span> <span class="nv">line</span> <span class="nf">withN:</span> <span class="nv">n</span>
  <span class="c">&#34;Returns an OrderedCollection of character ngrams with n = parameter n.&#34;</span>

  <span class="o">|</span><span class="nv"> ngrams lastChars </span><span class="o">|</span>
  <span class="nv">ngrams</span> <span class="o">:=</span> <span class="nc">OrderedCollection</span> <span class="nb">new</span><span class="p">.</span>
  <span class="nv">lastChars</span> <span class="o">:=</span> <span class="nc">OrderedCollection</span> <span class="nb">new</span><span class="p">.</span>
  
  <span class="nv">line</span> <span class="nf">do:</span> [ <span class="o">:</span><span class="nv">word</span> <span class="o">|</span>
    (<span class="nv">lastChars</span> <span class="nf">size</span> <span class="nf">&lt;</span> <span class="nv">n</span>) <span class="nb">ifFalse:</span> [
      <span class="nv">ngrams</span> <span class="nf">add:</span> (<span class="s">&#39;&#39;</span> <span class="nf">join:</span> <span class="nv">lastChars</span>)<span class="p">.</span>
      <span class="nv">lastChars</span> <span class="nf">removeFirst</span><span class="p">.</span>
      ]<span class="p">.</span>
      <span class="nv">lastChars</span> <span class="nf">addLast:</span> <span class="nv">word</span><span class="p">.</span>
   ]<span class="p">.</span>
  <span class="nv">ngrams</span> <span class="nf">add:</span> (<span class="s">&#39;&#39;</span> <span class="nf">join:</span> <span class="nv">lastChars</span>)<span class="p">.</span>
  
  <span class="o">^</span> <span class="nv">ngrams</span></code></pre></td></tr></table>
</div>
</div>
<p>Here is how output will look for <code>n=7</code> on <code>I am lucky</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">an</span> <span class="nf">OrderedCollection</span>(<span class="s">&#39;I am lu&#39;</span> <span class="err">&#39;</span> <span class="nf">am</span> <span class="nf">luc</span><span class="err">&#39;</span> <span class="err">&#39;</span><span class="nf">am</span> <span class="nf">luck</span><span class="err">&#39;</span> <span class="err">&#39;</span><span class="nf">m</span> <span class="nf">lucky</span><span class="err">&#39;</span>)</code></pre></td></tr></table>
</div>
</div>
<p>We now extend this to DataFrame, using the following code:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getNgramsFromDataFrame:</span> <span class="nv">df</span> <span class="nf">withN:</span> <span class="nv">n</span>
  <span class="c">&#34;Returns an array of word ngrams with n = parameter n.&#34;</span>

  
  <span class="o">^</span> ((<span class="nv">df</span> <span class="nf">column:</span> <span class="ss">#Message</span>) <span class="nf">collect:</span> [ <span class="o">:</span><span class="nv">line</span> <span class="o">|</span>
     <span class="bp">self</span> <span class="nf">getNgramsFromLine:</span> <span class="nv">line</span> <span class="nf">withN:</span> <span class="nv">n</span><span class="p">.</span>
     ]) <span class="nf">asArray</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>You can create a similar one for char-ngrams.</p>

<p>Try running it as follows:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">df</span> <span class="nf">addColumn:</span> (<span class="nc">ChatFeatures</span> <span class="nf">getNgramsFromDataFrame:</span> <span class="nv">df</span> <span class="nf">withN:</span> <span class="m">3</span>) <span class="nf">named:</span> <span class="ss">#Ngrams</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>Output looks like this:
<img src="/images/WApart2/ngramsDataFrame.png" alt="N-gram dataframe" /></p>

<p>Punctuations makes inference difficult eg: index 19 has <code>-</code> which is counted as a word. For n-grams to be effective, we need to clean our DataFrame. Let&rsquo;s add a few messages to make cleaning easier.</p>

<h2 id="setting-appropriate-column-type">Setting appropriate column type</h2>

<p>If you inspect the <code>df</code>, you&rsquo;ll notice that all columns are of type <code>ByteString</code>, except a few cells with <code>WideString</code>. Columns like Date, Time need to be in appropriate type so that analyzing them becomes easier.</p>

<p>We iterate through rows, transforming each cell to appropriate data type:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">setTypesFor:</span> <span class="nv">df</span>
   <span class="c">&#34;Transforms the df columns into appropriate types&#34;</span>

   <span class="nv">df</span> <span class="nf">do:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span>
   <span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Date</span> <span class="nf">transform:</span> [ <span class="o">:</span><span class="nv">date</span> <span class="o">|</span> <span class="nv">date</span> <span class="nf">asDate</span> ]<span class="p">.</span>
   <span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Time</span> <span class="nf">transform:</span> [ <span class="o">:</span><span class="nv">time</span> <span class="o">|</span> <span class="nv">time</span> <span class="nf">asTime</span> ]<span class="p">.</span>
   <span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Message</span> <span class="nf">transform:</span> [ <span class="o">:</span><span class="nv">message</span> <span class="o">|</span> <span class="nv">message</span> <span class="nf">asWideString</span> ]<span class="p">.</span>
   ]<span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>This should be placed as a class-side method in <code>ChatClenaer</code>. Usage:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nc">ChatCleaner</span> <span class="nf">setTypesFor:</span> <span class="nv">df</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>An alternative way to implement this would be using <code>toColumn: columnName applyElementwise: block</code> method, or setting it in <code>WhatsappReader</code> itself.</p>

<h2 id="making-messages-lowercase">Making messages lowercase</h2>

<p>For analysing word count, we need to ensure that words are in similar case since <code>'Word' ~= 'word'</code>. We apply a similar transformation <code>lowercase</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">messagesAsLowercase:</span> <span class="nv">df</span>
   <span class="c">&#34;Returns a DataSeries of the message column with lowercase strings&#34;</span>

   <span class="o">^</span> ((<span class="nv">df</span> <span class="nf">column:</span> <span class="ss">#Message</span>) <span class="nf">collect:</span> [ <span class="o">:</span><span class="nv">message</span> <span class="o">|</span> <span class="nv">message</span> <span class="nf">asLowercase</span> ])<span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>You can run it as:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">df</span> <span class="nf">column:</span> <span class="ss">#Message</span> <span class="nf">put:</span> (<span class="nc">ChatCleaner</span> <span class="nf">messagesAsLowercase:</span> <span class="nv">df</span>)<span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p><img src="/images/WApart1/cleaningOutput.png" alt="Cleaning output" /></p>

<h2 id="keep-words-only">Keep words only</h2>

<p>We need to remove punctuation so that when we split a string by <code>space</code>, we directly will get an array of words. The regex used to match non-words is <code>[^\w\s]</code>. <code>^</code> stands for <code>not</code> matching, <code>\w</code> stands for a series of letters, and <code>\s</code> stands for series of different spaces <code>space</code> <code>\t</code> <code>\r</code> <code>\n</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getWordsFrom:</span> <span class="nv">df</span>
   <span class="c">&#34;Returns a DataSeries with removes punctuation and digits from #Message&#34;</span>

   <span class="o">^</span> ((<span class="nv">df</span> <span class="nf">column:</span> <span class="ss">#Message</span>) <span class="nf">collect:</span> [ <span class="o">:</span><span class="nv">message</span> <span class="o">|</span>
       <span class="nv">message</span> <span class="nf">copyWithRegex:</span> <span class="s">&#39;[^\w\s]&#39;</span> <span class="nf">matchesReplacedWith:</span> <span class="s">&#39;&#39;</span>
       ]
     )</code></pre></td></tr></table>
</div>
</div>
<h2 id="keep-emojis-only">Keep emojis only</h2>

<p>To count emojis, we need a regex that will keep them only. However, their unicode ranges are too varied, and I found it easier just to match non-emoji characters and remove them using the regex <code>[\w\d\s\\:.,''&quot;-/?!()[]&lt;&gt;@’^“”=+_]</code> (It&rsquo;s just a regex with all symbols I have seen in chat mixed together).</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getEmojisFrom:</span> <span class="nv">df</span>
   <span class="c">&#34;Returns the #Message column while removing non-emoji characters. The regex might not work perfectly&#34;</span>

   <span class="o">^</span> ((<span class="nv">df</span> <span class="nf">column:</span> <span class="ss">#Message</span>) <span class="nf">collect:</span> [ <span class="o">:</span><span class="nv">message</span> <span class="o">|</span>
       <span class="nv">message</span> <span class="nf">copyWithRegex:</span> <span class="s">&#39;[\w\d\s\\:.,&#39;&#39;&#34;-/?!()[]&lt;&gt;@’^“”=+_]&#39;</span> <span class="nf">matchesReplacedWith:</span> <span class="s">&#39;&#39;</span>
       ]
     )</code></pre></td></tr></table>
</div>
</div>
<h2 id="removing-stopwords">Removing stopwords</h2>

<p>Stopwords are the words that do not give you any insight, such as <code>is</code>, <code>are</code>, <code>his</code>, <code>when</code> etc. The list of stopwords here is taken from sklearn, who have sourced it from &ldquo;Glasgow Information Retrieval Group&rdquo;. Define following to class-side messages in <code>ChatCleaner</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">getStopwords</span>
  <span class="c">&#34;Returns array of stopwords&#34;</span>

  <span class="o">^</span> <span class="nc">Set</span> <span class="nf">withAll:</span> <span class="ss">#(</span>
  <span class="s">&#39;list&#39;</span> <span class="s">&#39;is&#39;</span> <span class="s">&#39;removed&#39;</span>
  <span class="s">&#39;please&#39;</span> <span class="s">&#39;visit&#39;</span> <span class="ss">github</span><span class="s">&#39; &#39;</span><span class="ss">repo</span><span class="s">&#39; &#39;</span><span class="ss">for</span><span class="s">&#39; &#39;</span><span class="ss">complete</span><span class="s">&#39; &#39;</span><span class="ss">list</span><span class="err">&#39;</span>
  <span class="ss">)</span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">removeStopwordsFrom:</span> <span class="nv">df</span>
  <span class="c">&#34;Removes stop words and returns Message dataseries&#34;</span>

  <span class="o">|</span><span class="nv"> stopWords words </span><span class="o">|</span>
  <span class="nv">stopWords</span> <span class="o">:=</span> <span class="bp">self</span> <span class="nf">getStopwords</span><span class="p">.</span>
  <span class="o">^</span> (<span class="nv">df</span> <span class="nf">column:</span> <span class="ss">#Message</span>) <span class="nf">collect:</span> [ <span class="o">:</span><span class="nv">message</span> <span class="o">|</span>
   <span class="nv">words</span> <span class="o">:=</span> ((<span class="nv">message</span> <span class="nf">trimBoth</span>) <span class="nf">splitOn:</span> (<span class="s">&#39;\s+&#39;</span> <span class="nf">asRegex</span>))<span class="p">.</span>
   <span class="nv">words</span> <span class="o">:=</span> <span class="nv">words</span> <span class="nf">reject:</span> [ <span class="o">:</span><span class="nv">word</span> <span class="o">|</span>
     <span class="nv">stopWords</span> <span class="nf">includes:</span> (<span class="nv">word</span> <span class="nf">asLowercase</span>)
      ]<span class="p">.</span>
   <span class="s">&#39; &#39;</span> <span class="nf">join:</span> <span class="nv">words</span>
    ]</code></pre></td></tr></table>
</div>
</div>
<p>In the above snippet, we just reject input words present in stopWords and create an <code>OrderedCollection</code> out of those. To get words from message, we first remove any whitespace from both ends and the split using <code>\s+</code> regex, which matches one or more whitespace characters.</p>

<h2 id="removing-blacklisted-messages">Removing blacklisted messages</h2>

<p>There are messages like <code>This message was deleted</code> and <code>&lt;Media omitted&gt;</code> which would not contribute to text analysis. To remove such messages (and any additional ones) we add them to a set and pass it to the following method:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">removeMessages:</span> <span class="nv">blacklistSet</span> <span class="nf">from:</span> <span class="nv">df</span>
   <span class="c">&#34;Removes messages present in blacklistSet from df&#34;</span>

   <span class="o">|</span><span class="nv"> outputDf </span><span class="o">|</span>
   <span class="nv">outputDf</span> <span class="o">:=</span> <span class="nv">df</span> <span class="nf">reject:</span> [ <span class="o">:</span><span class="nv">row</span> <span class="o">|</span>
      <span class="nv">blacklistSet</span> <span class="nf">includes:</span> (<span class="nv">row</span> <span class="nf">at:</span> <span class="ss">#Message</span>)
      ]<span class="p">.</span>
   <span class="o">^</span> <span class="nv">outputDf</span>   </code></pre></td></tr></table>
</div>
</div>
<p>This can be used as:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">blacklist</span> <span class="o">:=</span> <span class="nc">Set</span> <span class="nf">withAll:</span> <span class="ss">#(</span><span class="s">&#39;This message was deleted&#39;</span> <span class="s">&#39;&lt;Media omitted&gt;&#39;</span><span class="ss">)</span><span class="p">.</span>
<span class="nv">nGramDf</span> <span class="o">:=</span> (<span class="nc">ChatCleaner</span> <span class="nf">removeMessages:</span> <span class="nv">blacklist</span> <span class="nf">from:</span> <span class="nv">nGramDf</span>)<span class="p">.</span>
<span class="c">&#34; If you run this on group0.txt, you will see 17 messages removed. &#34;</span>
(<span class="nv">df</span> <span class="nf">size</span>) <span class="nf">-</span> (<span class="nv">ngram</span> <span class="nf">size</span>)</code></pre></td></tr></table>
</div>
</div>
<p>I think that&rsquo;s all the messages needed! I&rsquo;ll add few more if I think they are missing while writing next parts. You can find the messages implemented here: <a href="https://github.com/AtharvaKhare/Whatsapp-Analyzer/" target="_blank">Whatsapp-Analyzer</a>.</p>

<p>In the next part, we&rsquo;ll analyze our parsed chat by using messages we have created.</p>
]]></content>
        </item>
        
        <item>
            <title>Building Whatsapp Analyzer in Pharo: Parsing (Part 1)</title>
            <link>https://khare.dev/posts/2019/04/building-whatsapp-analyzer-in-pharo-parsing-part-1/</link>
            <pubDate>Sun, 14 Apr 2019 08:55:44 +0530</pubDate>
            
            <guid>https://khare.dev/posts/2019/04/building-whatsapp-analyzer-in-pharo-parsing-part-1/</guid>
            <description>As a part of getting familiar with Dataframe library as well as Pharo, I decided to build a Whatsapp chat analyzer in it. This post puts down steps to parse the exported *.txt file into a Dataframe object.
If you are unfamiliar with Pharo, check out Pharo by Example as well as this blog post.
Prerequisites 1. Basics of Pharo/smalltalk You must know the basics of Pharo, such as installing packages, defining classes/subclasses, message passing etc.</description>
            <content type="html"><![CDATA[

<p>As a part of getting familiar with <a href="https://github.com/PolyMathOrg/DataFrame" target="_blank">Dataframe library</a> as well as Pharo, I decided to build a Whatsapp chat analyzer in it. This post puts down steps to parse the exported *.txt file into a Dataframe object.</p>

<p>If you are unfamiliar with Pharo, check out <a href="http://pharo.gforge.inria.fr/PBE1/PBE1ch1.html" target="_blank">Pharo by Example</a> as well as <a href="https://medium.com/smalltalk-talk/pharo-the-future-of-software-development-1eff6240c60b" target="_blank">this blog post</a>.</p>

<h2 id="prerequisites">Prerequisites</h2>

<h4 id="1-basics-of-pharo-smalltalk">1. Basics of Pharo/smalltalk</h4>

<p>You must know the basics of Pharo, such as installing packages, defining classes/subclasses, message passing etc. These are well covered in the above linked blog post and <code>Pharo by Example</code> (first 3 chapters are enough for this post).</p>

<h4 id="2-whatsapp-chat">2. Whatsapp chat</h4>

<p>The first thing we need is a Whatsapp Chat log. Open any Whatsapp group or personal chat, tap on <code>options</code> (3 dots on top right), then on <code>more</code>  and select <code>Export chat</code> (without media). You may either save it to your Google Drive, email the chat to yourself or send it to someone in your contact and download it.</p>

<p><img src="/images/WApart1/WhatsappExport.png" alt="Whatsapp Export" /></p>

<p>I have uploaded a sample Whatsapp group chat at <a href="/dont_crawl/group0.txt">this link</a>, which you may use for following this tutorial. The sample has 247 lines from 50 users.</p>

<h4 id="3-pharo-with-dataframe-library">3. Pharo with Dataframe library</h4>

<p>You need Pharo VM with Dataframe library installed. For this post, I used Pharo 7.0. You will find the instructions to install and use it in the book <code>Pharo by Example</code>.</p>

<p>Open playground, copy paste the following, and run it. It will install the <a href="https://github.com/PolyMathOrg/DataFrame" target="_blank">Dataframe library</a>. Save the image so that you won&rsquo;t need to install it again.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nc">Metacello</span> <span class="nb">new</span>
  <span class="nf">baseline:</span> <span class="s">&#39;DataFrame&#39;</span><span class="p">;</span>
  <span class="nf">repository:</span> <span class="s">&#39;github://PolyMathOrg/DataFrame/src&#39;</span><span class="p">;</span>
  <span class="nf">load</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>An excellent resource for learning about this library is <a href="https://github.com/SquareBracketAssociates/Booklet-DataFrame" target="_blank">Dataframe by Example</a>, which goes over the messages that you can use, and scenarios in which you might use them.</p>

<h2 id="reading-the-chat">Reading the chat</h2>

<h3 id="parsing-lines">Parsing lines</h3>

<p>Here is how a line in the exported chat looks like:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text">4/10/19, 6:18 PM - Member14: Don&#39;t have a surname, it&#39;s empty in passport as well, what to fill in access via form in surname field, name?</code></pre></td></tr></table>
</div>
</div>
<p>We get four fields from a line - Date, Time, Author, and Message, in the following format:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text">&lt;d/m/yy&gt;, &lt;h:mm (AM/PM)&gt; - &lt;author&gt;: &lt;message&gt;</code></pre></td></tr></table>
</div>
</div>
<p>Date occurs till the first <code>,</code>, time from <code>,</code> to <code>-</code>, author from <code>-</code> till second <code>:</code> (since time has a <code>:</code> in it) and message is the rest of the line.</p>

<p>However, a chat consists of non-message lines when a group name is changed, photo is updated, or members are removed or added, such as these:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text">4/10/19, 8:08 AM - Messages to this group are now secured with end-to-end encryption. Tap for more info.
3/28/19, 8:39 AM - Member0 created group &#34;GROUPNAME&#34;
4/10/19, 8:08 AM - Member0 added you
4/10/19, 8:29 AM - Member0 added Member4
4/10/19, 8:36 AM - Member0 added Member6</code></pre></td></tr></table>
</div>
</div>
<p>These can be detected as they (mostly) do not have more than one <code>:</code>. We ignore such lines since they are system generated and not users&rsquo; messages.
Some of the messages may be multiline, such as:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-text" data-lang="text">4/10/19, 9:44 AM - Member5: Hi
Even I got the admit last night only.
Did go thru the links here, pretty informative.
So the next step is just to accept and send transcripts right? Anything else for the time being ?</code></pre></td></tr></table>
</div>
</div>
<p>An easy way to detect them is checking if a line starts with a date regex, <code>\d(\d)?/\d(\d)?/\d\d</code>. You can learn what regexes are online and test them at regex101. The lines that match regex should be added as a new row, and those which don&rsquo;t should be appended to previous row.</p>

<p>We&rsquo;ll use the above four fields as columns in our dataframe along with the checks discussed and parse the lines. It would be easier for us to parse if we define a custom class with appropriate messages. We create a class <code>WhatsappReader</code> inside <code>WhatsappAnalyzer</code> package.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nc">DataFrameReader</span> <span class="nf">subclass:</span> <span class="ss">#WhatsappReader</span>
  <span class="nf">instanceVariableNames:</span> <span class="s">&#39;&#39;</span>
  <span class="nf">classVariableNames:</span> <span class="s">&#39;&#39;</span>
  <span class="nf">package:</span> <span class="s">&#39;WhatsappAnalyzer&#39;</span></code></pre></td></tr></table>
</div>
</div>
<p>The parent class is <code>DataFrameReader</code> since it allows us to use <code>DataFrame readFrom: using:</code> method. This is discussed in the next section. Add the following method into class-side:
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="hl"><span class="lnt"> 8
</span></span><span class="hl"><span class="lnt"> 9
</span></span><span class="hl"><span class="lnt">10
</span></span><span class="hl"><span class="lnt">11
</span></span><span class="hl"><span class="lnt">12
</span></span><span class="hl"><span class="lnt">13
</span></span><span class="hl"><span class="lnt">14
</span></span><span class="hl"><span class="lnt">15
</span></span><span class="hl"><span class="lnt">16
</span></span><span class="hl"><span class="lnt">17
</span></span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">insert:</span> <span class="nv">line</span> <span class="nf">into:</span> <span class="nv">df</span>
  <span class="c">&#34;Parses given line and inserts it as a row in dataframe. An utility method.&#34;</span>

  <span class="o">|</span><span class="nv"> commaIndex hyphenIndex colonIndex oldRow </span><span class="o">|</span>
  (<span class="nv">line</span> <span class="nf">isNotEmpty</span>) <span class="nb">ifTrue:</span> [
    ((<span class="nv">line</span> <span class="nf">copyUpTo:</span> <span class="sc">$,</span>) <span class="nf">matchesRegex:</span> <span class="s">&#39;\d(\d)?/\d(\d)?/\d\d&#39;</span>) 
      <span class="nb">ifTrue:</span> [ 
<span class="hl">        <span class="nv">commaIndex</span> <span class="o">:=</span> <span class="nv">line</span> <span class="nf">indexOf:</span> <span class="sc">$,</span><span class="p">.</span>
</span><span class="hl">        <span class="nv">hyphenIndex</span> <span class="o">:=</span> <span class="nv">line</span> <span class="nf">indexOf:</span> <span class="sc">$-</span><span class="p">.</span>
</span><span class="hl">        <span class="nv">colonIndex</span> <span class="o">:=</span> <span class="nv">line</span> <span class="nf">indexOf:</span> <span class="sc">$:</span> <span class="nf">startingAt:</span> <span class="nv">hyphenIndex</span><span class="p">.</span>
</span><span class="hl">        (<span class="nv">colonIndex</span> <span class="nf">~=</span> <span class="m">0</span> ) <span class="nb">ifTrue:</span> [
</span><span class="hl">          <span class="nv">df</span> <span class="nf">addRow:</span> { 
</span><span class="hl">            <span class="nv">line</span> <span class="nf">copyUpTo:</span> <span class="sc">$,</span> <span class="p">.</span>
</span><span class="hl">            <span class="nv">line</span> <span class="nf">copyFrom:</span> <span class="nv">commaIndex</span><span class="nf">+</span><span class="m">2</span> <span class="nf">to:</span> <span class="nv">hyphenIndex</span><span class="nf">-</span><span class="m">2</span> <span class="p">.</span>
</span><span class="hl">            <span class="nv">line</span> <span class="nf">copyFrom:</span> <span class="nv">hyphenIndex</span><span class="nf">+</span><span class="m">2</span> <span class="nf">to:</span> <span class="nv">colonIndex</span><span class="nf">-</span><span class="m">1</span> <span class="p">.</span>
</span><span class="hl">            (<span class="nv">line</span> <span class="nf">copyFrom:</span> <span class="nv">colonIndex</span><span class="nf">+</span><span class="m">1</span> <span class="nf">to:</span> <span class="nv">line</span> <span class="nf">size</span>) <span class="nf">allButFirst</span>
</span><span class="hl">          } <span class="nf">named:</span> <span class="nv">df</span> <span class="nf">numberOfRows</span> <span class="nf">+</span> <span class="m">1</span><span class="p">.</span>
</span>        ]
      ]
      <span class="nb">ifFalse:</span> [
        <span class="nv">oldRow</span> <span class="o">:=</span> <span class="nv">df</span> <span class="nf">row:</span> <span class="nv">df</span> <span class="nf">numberOfRows</span><span class="p">.</span>
        <span class="nv">oldRow</span> <span class="nf">at:</span> <span class="s">&#39;Message&#39;</span> <span class="nf">put:</span> ((<span class="nv">oldRow</span> <span class="nf">at:</span> <span class="s">&#39;Message&#39;</span>) <span class="nf">,</span> <span class="s">&#39;\n&#39;</span> <span class="nf">,</span> <span class="nv">line</span>)<span class="p">.</span>
        <span class="nv">df</span> <span class="nf">removeRow:</span> <span class="nv">df</span> <span class="nf">numberOfRows</span><span class="p">.</span>
        <span class="nv">df</span> <span class="nf">addRow:</span> <span class="nv">oldRow</span><span class="p">.</span>
      ]
  ]</code></pre></td></tr></table>
</div>
</div>
Lines 8 to 17 are used to parse a valid message line. We copy parts of the line using <code>copyUpTo:</code> and <code>copyFrom: to:</code> messages, and add them to the dataframe using <code>addRow: named:</code> message. Note that we use <code>{ }</code>, which denotes a dynamic array.</p>

<p>Line 6 checks if the message starts with a valid date, if it doesn&rsquo;t, it appends the line to previous row using 21 to 24.</p>

<p>Line 11 serves as a check for valid message line - if the line doesn&rsquo;t have a second <code>:</code>, it is a line that we are not interested in, such as when group name is changed.</p>

<p>You can try it out using the following code in Playground. Try all the possible lines (valid/invalid/multiline).</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">line</span> <span class="o">:=</span> <span class="s">&#39;4/10/19, 6:18 PM - Member14: Don&#39;&#39;t have a surname, it&#39;&#39;s empty in passport as well, what to fill in access via form in surname field, name?&#39;</span><span class="p">.</span>
<span class="nv">df</span> <span class="o">:=</span> <span class="nc">DataFrame</span> <span class="nf">withColumnNames:</span> <span class="ss">#(</span><span class="s">&#39;Date&#39;</span> <span class="s">&#39;Time&#39;</span> <span class="s">&#39;Author&#39;</span> <span class="s">&#39;Message&#39;</span><span class="ss">)</span><span class="p">.</span>
<span class="nc">WhatsappReader</span> <span class="nf">insert:</span> <span class="nv">line</span> <span class="nf">into:</span> <span class="nv">df</span><span class="p">.</span>
<span class="c">&#34;Inspect df to see the change.&#34;</span></code></pre></td></tr></table>
</div>
</div>
<h3 id="reading-the-chat-file">Reading the chat file</h3>

<p><code>DataFrame</code> library has a method <code>readFrom: using:</code> which enables us to write custom readers which can enter data into the dataframe. One such reader is <code>DataFrameCsvReader</code> whose implementation can be found <a href="https://github.com/PolyMathOrg/DataFrame/blob/master/src/DataFrame-IO/DataFrameCsvReader.class.st" target="_blank">here</a>.</p>

<p>To achieve similar result, we have inherited the <code>DataFrameReader</code> class, which makes it <code>readFrom:</code> a subclass responsiblity. Now we need to override that method, and we do so as follows:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">readFrom:</span> <span class="nv">chatFileReference</span>
  <span class="c">&#34;Read Whatsapp chat and add it into the dataframe. Takes FileRefernce as it&#39;s argument&#34;</span>
  
  <span class="o">|</span><span class="nv"> df line </span><span class="o">|</span>
  <span class="nv">df</span> <span class="o">:=</span> <span class="bp">self</span> <span class="nf">createDataFrame</span><span class="p">.</span>
  <span class="nv">chatFileReference</span> <span class="nf">readStreamDo:</span> [ <span class="o">:</span><span class="nv">inputStream</span> <span class="o">|</span>
    [ <span class="nv">inputStream</span> <span class="nf">atEnd</span> ] <span class="nb">whileFalse:</span> [ 
      <span class="nv">line</span> <span class="o">:=</span> <span class="nv">inputStream</span> <span class="nf">nextLine</span><span class="p">.</span>
      <span class="bp">self</span> <span class="nf">class</span> <span class="nf">insert:</span> <span class="nv">line</span> <span class="nf">into:</span> <span class="nv">df</span><span class="p">.</span>
      ]
     ]<span class="p">.</span>
  <span class="o">^</span> <span class="nv">df</span></code></pre></td></tr></table>
</div>
</div>
<p>We read the <code>FileReference</code> line by line using <code>readStreamDo</code> and <code>nextLine</code>. <code>inputStream atEnd</code> ensures that we have read all lines. <code>readStreamDo</code> closes the stream when the block is executed. <code>createDataFrame</code> has been defined as follows:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nf">createDataFrame</span> 
  <span class="c">&#34;Creates an empty dataframe with four columns parsed from Whatsapp chat&#34;</span>

  <span class="o">^</span> <span class="nc">DataFrame</span> <span class="nf">withColumnNames:</span> <span class="ss">#(</span><span class="s">&#39;Date&#39;</span> <span class="s">&#39;Time&#39;</span> <span class="s">&#39;Author&#39;</span> <span class="s">&#39;Message&#39;</span><span class="ss">)</span></code></pre></td></tr></table>
</div>
</div>
<p>Now, we finally can run the program to get parsed chat.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-smalltalk" data-lang="smalltalk"><span class="nv">fileRef</span> <span class="o">:=</span> <span class="s">&#39;/path/to/group0.txt&#39;</span> <span class="nf">asFileReference</span><span class="p">.</span>
<span class="nv">reader</span> <span class="o">:=</span> <span class="nc">WhatsappReader</span> <span class="nb">new</span><span class="p">.</span>
<span class="nv">df</span> <span class="o">:=</span> <span class="nc">DataFrame</span> <span class="nf">readFrom:</span> <span class="nv">fileRef</span> <span class="nf">using:</span> <span class="nv">reader</span><span class="p">.</span></code></pre></td></tr></table>
</div>
</div>
<p>Inspecting <code>df</code>, we see the following:
<img src="/images/WApart1/ParserOutput.png" alt="Parser Output" />
We were able to parse 220 lines of data (including multiline), and ignored 27 lines.</p>

<p>The resulting code is available at my GitHub: <a href="https://github.com/AtharvaKhare/Whatsapp-Analyzer/" target="_blank">Whatsapp-Analyzer</a>.</p>

<p>In the following parts, we&rsquo;ll clean the data, find interesting metrics on it, and plot them!</p>
]]></content>
        </item>
        
    </channel>
</rss>
