<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://psrikanthm.github.io//feed.xml" rel="self" type="application/atom+xml" /><link href="https://psrikanthm.github.io//" rel="alternate" type="text/html" /><updated>2026-06-27T20:36:18+00:00</updated><id>https://psrikanthm.github.io//feed.xml</id><title type="html">OneSevenTwoNine</title><subtitle>Personal blog on software engineering, data science and machine learning</subtitle><entry><title type="html">Profiling Myself, But Locally</title><link href="https://psrikanthm.github.io//personal-profiler/" rel="alternate" type="text/html" title="Profiling Myself, But Locally" /><published>2026-06-27T00:00:00+00:00</published><updated>2026-06-27T00:00:00+00:00</updated><id>https://psrikanthm.github.io//personal-profiler</id><content type="html" xml:base="https://psrikanthm.github.io//personal-profiler/"><![CDATA[<p>I am building a personal data profiler: a system that collects parts of my digital footprint and helps me make sense of them. That sounds creepy, and it should. Most profiling systems are creepy because they are built by someone else, for someone else’s incentives. They turn our behaviours into predictions for ads, feeds, and retention loops. I’m interested in a different version - profiling as a way to understand myself, <em>helping me gauge whether my intentions and my attention are aligned</em>.</p>

<p>Because we use different cloud services, our own digital footprint is highly scattered - bank portals, Chrome browser, Garmin, YouTube, Goodreads etc. So, the first challenge is simply getting the data into one place and normalizing it. To enrich and categorize the free-form text in the data, traditionally would have involved supervised Natural Language Processing (NLP) models and doing that on your own data would have been very difficult. However, we are at a point in our LLM technology, where these NLP tasks can often be handled with few-shot prompts. Especially, if we can build the profiler using local LLM models, we will have a system that is confined to a boundary that we control and understand what it is doing. In the past I have built <a href="https://github.com/psrikanthm/expense-report">expense reporter</a> based on my credit card and bank statements. Now, I have worked on building insights from my <strong>browsing history</strong>.</p>

<p>The architecture for the browsing history profiler at a high level is same as what I talked about in my post on <a href="https://psrikanthm.github.io/data-product/">Anatomy of a Data Product</a>. The interesting part in this pipeline is that high-level topics are not assigned one page at a time. They are synthesized globally from the full browsing corpus, so the taxonomy reflects my actual library rather than a generic topic list. The main four stages of the data pipeline are:</p>

<ul>
  <li><strong>Capture:</strong> A Chrome extension sends bookmarks and browsing history to the service.</li>
  <li><strong>Enrich:</strong> The service extracts article text, detailed topics, and vector embeddings.</li>
  <li><strong>Store:</strong> SQLite keeps captures, topics, high-level topics, and UMAP coordinates. ChromaDB stores embeddings and metadata.</li>
  <li><strong>Synthesize:</strong> A batch job builds a global taxonomy and computes the graph layout.</li>
</ul>

<p><img src="/images/personal-profiler/pipeline.png" alt="Browsing history profiler pipeline" /></p>

<p>To explore my browsing and bookmarks data, I have a simple React frontend where each node in the graph represents one page, and nodes are clustered based on their text semantics, and high-level and low-level topics make it easy for me to zoom in. Looking at the graph, I can see that software engineering and AI dominate my reading. Entrepreneurship shows up as a meaningful secondary cluster. These top topics don’t surprise me as much. But, some areas I generally care about, like travelling, parenting, tennis are much thinner or non-existent. That gap is useful: the system is not just reflecting my interests, it is showing me where my attention and my intentions diverge.</p>

<p><em>To protect my privacy, the screenshot below uses a deliberately biased sample.</em></p>

<p><img src="/images/personal-profiler/browsing-profiler.png" alt="Browsing history graph in the personal profiler" /></p>

<p>I would like to continue building these profilers that make sense of our own digital footprint in different domains, and aim to gain meaningful insights and recommendations that balance likeness and <strong>serendipity</strong>. I also think a lot more interesting insights would come out if we can cross-correlate the data from different verticals - Finance, Health, Reading, Media etc.</p>

<p>Personalization does not have to belong to platforms. With local models and local data, we can build profilers that serve self-understanding and can act as an assistant that keeps the user’s best interests in mind.</p>]]></content><author><name></name></author><category term="Local LLM" /><category term="Personal Data" /><category term="Data Product" /><category term="Browsing History" /><summary type="html"><![CDATA[Building a personal data profiler locally, a system that collects parts of my digital footprint and helps me make sense of them.]]></summary></entry><entry><title type="html">Snakes and Ladders - Simulation, Markov Chains, and Board Design</title><link href="https://psrikanthm.github.io//snakes-ladders/" rel="alternate" type="text/html" title="Snakes and Ladders - Simulation, Markov Chains, and Board Design" /><published>2026-02-28T00:00:00+00:00</published><updated>2026-02-28T00:00:00+00:00</updated><id>https://psrikanthm.github.io//snakes-ladders</id><content type="html" xml:base="https://psrikanthm.github.io//snakes-ladders/"><![CDATA[<h1 id="snakes-and-ladders-simulation-markov-chains-and-board-design">Snakes and Ladders: Simulation, Markov Chains, and Board Design</h1>

<p>Lately, my son has been obsessed with the game of Snakes and Ladders. So much so that he made it a mission to win the game first before he does any thing else. Watching him play so many games, I got curious about the game mechanics and design. In this post I explored questions like - what is the expected number of turns in a game, what a boring Snakes and Ladders board with extremely low variance would look like, and conversely, how to mathematically design a chaotic board where the variance is very high.</p>

<h2 id="simulating-a-game">Simulating a Game</h2>

<p>Let’s start by simulating a single game with two players on the board that we got from the store.</p>

<p><img src="/images/snakes-ladders/animation_89ca8dbd.gif" alt="Animation" /></p>

<h2 id="understanding-game-statistics-via-monte-carlo">Understanding Game Statistics via Monte Carlo</h2>

<p>Now, lets run hundred thousand simulations (Monte Carlo method) on this board to get an estimate of the expected number of turns it takes to win and see the distribution of turns. First we will run this simulation when only one player is playing!</p>

<p>This distribution makes it look like a well balanced board overall, there are few unlucky ones who would have gotten eaten by big snakes multiple times (100+ turns). By eye-balling the right-skewed probability distribution, we can see most games finish in 15 to 50 turns, with a long tail of unlucky outliers..</p>

<p><img src="/images/snakes-ladders/blog_post_5_0.png" alt="png" /></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Mean: 43.82096308326311, Variance: 752.33833145564, Standard Deviation: 27.42878654726891
</code></pre></div></div>

<h3 id="would-the-game-be-quicker-if-there-are-more-players-">Would the game be quicker if there are more players ?</h3>

<p>You can see below the average number of turns each player takes before someone wins. It does make intuitive sense that each player takes lesser turns, as there is higher chance of someone lucking out (falling in left side portions of probability distribution function) when there are more players.
Though that doesn’t mean the game finshes quickly in the sense of time! As the total game turns itself among all players is a multiple of expected turns for each player, plus the amount of drama increases exponentially with each player :smiley:</p>

<p><img src="/images/snakes-ladders/blog_post_7_0.png" alt="png" /></p>

<h2 id="markov-chain-analysis">Markov Chain Analysis</h2>

<p>Monte Carlo simulations are great to calculate the expected number of turns, thereby helping us to understand the chances in a board. But they are also computationally expensive, especially if one wants to try out different board variations to optimize for certain factors. To our rescue are Markov Chains! Snakes and Ladders is a classic example of a Markov Chain.</p>

<ul>
  <li>The game has a finite number of states (squares on the board).</li>
  <li>The probability of moving from one square to another depends only on the current square, not the history of the game.</li>
  <li>Say you are on square 10, and there is a ladder at square 12 -&gt; 24, there is a snake at square 15 -&gt; 6, the 10th row of transition matrix looks like this (0…1/6 0 0 0 0 1/6 0 1/6 1/6 0 1/6 …. 1/6 0 … ). Where the probability of each of 10 -&gt; {6, 11, 24, 13, 14, 16} transitions have 1/6th probability and rest of the transitions have 0 probability.</li>
</ul>

<p>By representing the board as a transition matrix and treating 100 as absorbing square, we can calculate the exact expected number of turns and the variance without running any simulations! The math behind calculation of expected number of transitions and variation in number of transitions can be found <a href="https://ocw.mit.edu/courses/6-041sc-probabilistic-systems-analysis-and-applied-probability-fall-2013/pages/unit-iii/lecture-18/">here</a>. Let’s define a board and compare the exact Markov Chain statistics with our Monte Carlo simulation results.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Stats calculated with Monte Carlo Simulation: Mean: 44.90337, Variance: 757.3198526431, Standard Deviation: 27.519444991552792
Stats calculated with Markov Chain Process: Mean: 43.82096308326311, Variance: 752.33833145564, Standard Deviation: 27.42878654726891
</code></pre></div></div>

<p><strong>More power to the law of large numbers!! The mean, variance, standard deviation calculated with Markov Chain Statistical Model are almost same as the ones we calculated from 100000 Monte Carlo Simulations.</strong></p>

<h2 id="designing-snakes-and-ladder-board-design">Designing Snakes And Ladder Board Design</h2>

<p><em>Now comes the interesting part.</em></p>

<p>The board we have been analyzing so far is based on what we got from the store. I’d say its a well designed board, balancing the excitement with boredom. Now, how can we design boards optimized for special cases. Say we want a board that is very chaotic, where its hard to predict how many turns it is going to take to complete the game. How about a boring board, where no matter who plays the number of turns thereby the time taken is pretty much the same.</p>

<p>Here we design the boards for these extreme cases, demonstrating our optimization algorithm. Before we do though, a bit on the theory:</p>

<h3 id="optimization-algorithm">Optimization Algorithm</h3>

<p>We want the algorithm to figure out where to place the snakes and ladders so as to achieve the objective we are choosing for. This is a combinatorial problem, and considering the number of overall possibilities, we can’t brute force our way out of it. And here, we aren’t trying to solve for a theoretically bounded solution but a solution that we can arguable verify that it is closer to our objective.</p>

<p>Such optimization algorithm typically has three ingredients:</p>

<p><strong>1) Loss function</strong></p>

<p>The loss function we choose for is, how much the mean and standard deviation of number of turns differs from the target. So closer the mean and std to our targets, smaller the loss. <code class="language-plaintext highlighter-rouge">weight_mean</code> and <code class="language-plaintext highlighter-rouge">weight_std</code> are hyperparameters to tune for, indicating which of the component of loss function is more important.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>loss = weight_mean * abs(mean - target_mean) + weight_std * abs(std - target_std)
</code></pre></div></div>

<p><strong>2) Neighbour selection function</strong></p>

<p>Given certain locations of snakes and the ladders, this function outputs an altered positions of these snakes and ladders, resulting in a new board. The way we alter the positions could be very wild - flipping the direction of snake/ladder, adding a new snake/ladder, swapping the positions of snake to ladder or vice versa etc. Or the alterations could be very minimal - making the snake/ladder bigger/shorter by few squares, sliding the snake/ladder but keeping length the same etc. In re-inforcement learning terminology, these moves can be categorized as exploration and exploitation respectively. Exploration moves especially at the beginnning of the game are useful to literally explore possibilities trying to get in the neighbourhood of low “energy” regions. Whereas, exploration moves are important to tune the board to get closer and closer to the objective.</p>

<p>My Neighbour function picks exploration move with high probability at the earlier iterations, and chooses exploitation move with high probability in final stages.</p>

<p>In below, <code class="language-plaintext highlighter-rouge">_T</code> refers to Temperature, which will talk about as part of next ingredient.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cooling_ratio = current_T / max_T

explore_pct = 0.05 + (0.55 * cooling_ratio) 

explore_moves = ['add_delete', 'flip', 'swap']
exploit_moves = ['slide', 'shift', 'stretch']

if random.random() &lt; explore_pct:
    move_type = random.choices(explore_moves, weights=[0.5, 0.3, 0.2])[0]
else:
    move_type = random.choices(exploit_moves, weights=[0.4, 0.4, 0.2])[0]
</code></pre></div></div>
<p>I have also put some constraints on how the board should look like, such as it should contain at least 5 snakes/ladders and at most 10, the snake/ladder shouldn’t end in same row. This is to avoid weird looking boards.</p>

<p><strong>3) Iterative Loop</strong></p>

<p>This is where we orchestrate the overall algorithm. We start with a random initialization of the board, and ask our Neighbour function to make a new board out of it, then we measure the loss value of the new board using the above Markov Evaluator. If the new board has lower loss, we always accept it. If the new board has higher loss than the previous version, we might still accept it with a probability that is proportional to the current temperature (<code class="language-plaintext highlighter-rouge">math.exp(-delta_E / current_T)</code>). The reason to accept such bad boards is to potentially escape local minima, and explore other possibilities especially at the beginning of the algorithm.</p>

<p>This is the same temperature that we use to derive exploitation probability in Neighbour function. Temperature can be thought of as a “risk tolerance” parameter that controls the “wildness” or randomness of the algorithm. Typically, we start with a very high temperature (<code class="language-plaintext highlighter-rouge">max_T</code>) and decay it in every step with a scheduled cooling ratio. So basically, at the earlier iterations of the algorithm we are very open to wild board and after some iterations we try to minimize the risk.</p>

<p>One note on cooling ratio is, higher this ratio and faster is the algorithm at the risk of not exploring lot of solutions.</p>

<h3 id="boring-board">Boring board</h3>

<p>To design a boaring board, we set target mean as something resonable like 45 and set target standard deviation as 0</p>

<p>The algorithm after running ~2000 iterations produces very good, which I mean very boring board with an average of ~28 turns and a standard deviation of only ~8 turns. That means most of the the games should have 17 to 29 turns, compared to 15 to 50 turns of the original board.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Store-bought board stats: Mean: 43.82096308326311, Variance: 752.33833145564, Standard Deviation: 27.42878654726891
Boring board stats: Mean: 29.769746404989455, Variance: 64.5211715766194, Standard Deviation: 8.032507178746831
</code></pre></div></div>

<p><img src="/images/snakes-ladders/blog_post_13_1.png" alt="png" /></p>

<p>By looking at the boring board, there are definitely bare minimum snakes and ladders. Especially, the snakes are very small and they seem to be placed not to hurt you but to satisfy the constraints!</p>

<p><img src="/images/snakes-ladders/animation_a78e15e5.gif" alt="Animation" /></p>

<h3 id="chaotic-board">Chaotic board</h3>

<p>Maths says that to design a chaotic or very uncertain board where we can’t really tell how long the game is going to last, we’d need to target for very high standard deviation. So we choose a reasonable mean, and a standard deviation that is higher than the mean itself.</p>

<p>Now, let’s try the opposite: a highly “chaotic” board. We’ll set the target mean to 45 turns again, but increase the target standard deviation to 50. This creates a highly unpredictable board where games could end extremely quickly or take forever!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Store-bought board stats: Mean: 43.82096308326311, Variance: 752.33833145564, Standard Deviation: 27.42878654726891
Chaotic board stats: Mean: 53.554156747278846, Variance: 4891.157442564327, Standard Deviation: 69.93681035452165
</code></pre></div></div>

<p><img src="/images/snakes-ladders/blog_post_17_1.png" alt="png" /></p>

<p>I really love this chaotic board! Its like either you get those very first 15, 17, 18, 19 ladders or you pretty much don’t get any help at all afterwords. Plus the snakes at 67, 68, 69, 70, 71 are like “almost” traps to increase number of turns in the game, thereby increasing the std deviation. Not sure how the algorithm came up with these placements but it is such a beautiful idea to satisfy the given loss function.</p>

<p><img src="/images/snakes-ladders/animation_c12aaf06.gif" alt="Animation" /></p>

<h2 id="conclusion">Conclusion</h2>

<p>We have used two extreme examples to illustrate the power of stochastic optimization, and similar strategy could be use to design things for whatever value function. While this is a powerful framework that can be applied across domains and problems, designing the Loss and Neighbour functions need deep understanding of the domain. Plus in my opinion its more of an art to come up with these functions. That makes this framework even more beautiful.</p>

<p><em>Source code for simulation: <a href="https://github.com/psrikanthm/snakes-and-ladder">https://github.com/psrikanthm/snakes-and-ladder</a></em></p>]]></content><author><name></name></author><category term="Stochastic Optimization" /><category term="Monte Carlo Simulation" /><category term="Markov Chain Process" /><summary type="html"><![CDATA[Lately, my son has been obsessed with the game of Snakes and Ladders. So much so that he made it a mission to win the game first before he does any thing else. Watching him play so many games, I got curious about the game mechanics and design. In this post I explored questions like - what is the expected number of turns in a game, what a boring Snakes and Ladders board with extremely low variance would look like, and conversely, how to mathematically design a chaotic board where the variance is very high.]]></summary></entry><entry><title type="html">Anatomy of a Data Product</title><link href="https://psrikanthm.github.io//data-product/" rel="alternate" type="text/html" title="Anatomy of a Data Product" /><published>2026-01-25T00:00:00+00:00</published><updated>2026-01-25T00:00:00+00:00</updated><id>https://psrikanthm.github.io//data-product</id><content type="html" xml:base="https://psrikanthm.github.io//data-product/"><![CDATA[<p>I define a <strong>Data Product</strong> as an application where the data itself is the primary feature, not just a byproduct. In these products, the UI serves mainly as a wrapper to filter, search, and visualize the underlying dataset. A few examples that fit in this definition are Business Intelligence Dashboards, Web Search interfaces, Credit scores in banking app.</p>

<p>The key components I see in building a Data Product are:</p>
<ul>
  <li>Data Pipelines - Usually <em>offline</em> jobs, that pull raw data from various sources and transform to domain objects</li>
  <li>Database - Stores the data</li>
  <li>Relevance Engine- Algorithms that help the user discover relevant data. For example, Netflix’s recommendation algorithm would surface a handful of titles among say million other options.</li>
  <li>Service layer + UI - APIs that serves the data to match with user’s intent. And UI to capture user’s intent and present relevant data.</li>
</ul>

<p>I would like to talk about the above components, by walking through the <a href="https://github.com/psrikanthm/expense-report">Categorized Expense Report</a> that I wrote for my own use.</p>

<h5 id="data-pipelines">Data Pipelines</h5>
<p>The offline jobs that I wrote for ingesting and processing my bank and credit card statements are:</p>

<p>1) CSV parsers to <a href="https://github.com/psrikanthm/expense-report/blob/main/src/expense_report/jobs.py#L27">parse</a> different bank and credit card statements to put in standardized data model</p>

<p>2) Keyword based <a href="https://github.com/psrikanthm/expense-report/blob/main/src/expense_report/jobs.py#L80-L124">categorizer</a> and categorizer using <strong>Local LLM</strong>, figures out a pre-defined category for any financial transaction.</p>

<p>3) Simple <a href="https://github.com/psrikanthm/expense-report/blob/main/src/expense_report/jobs.py#L153-L211">aggregator</a> condenses the spending of each category per month.</p>

<h4 id="database">Database</h4>
<p>In my toy project, I simply used CSV files for data storage. I followed <a href="https://www.databricks.com/glossary/medallion-architecture">medallion architecture</a> to organize my data:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Layer</th>
      <th style="text-align: right">Schema</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Bronze</td>
      <td style="text-align: right">As is from CIBC, Amex, Scotia bank statement downloads</td>
    </tr>
    <tr>
      <td style="text-align: left">Silver (Normalized)</td>
      <td style="text-align: right">Parsed, Normalized, Deduped into standard transactions schema</td>
    </tr>
    <tr>
      <td style="text-align: left">Silver (Enriched)</td>
      <td style="text-align: right">Standard transactions schema with an additional column of business logic based expense category. Think of it as enriched data</td>
    </tr>
    <tr>
      <td style="text-align: left">Gold</td>
      <td style="text-align: right">Aggregation of expense by month + category</td>
    </tr>
  </tbody>
</table>

<p>The Gold layer is readily available to be presented to the user and offer MoM expense insights I was looking for. At the same time, this sort of organizing data allows to expand to more use cases (anomaly detection, merchant level analysis etc) starting from the same Silver layer. My understanding of Medallion architecture is - Bronze is very wide and diverse, a reflection of wide variety of data input sources. Gold is also wide and diverse, a reflection of data use cases. Whereas Silver is the source of truth and expected to be very dense, the schema here closely resembles the business objects.</p>

<p>Interestingly, medallion architecture closely resembles three tiered architecture of standard backend service.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>View Model (Gold) &lt;-&gt; Domain (Silver) &lt;-&gt; DAO (Bronze)
</code></pre></div></div>

<h4 id="service-layer--ui">Service layer + UI</h4>

<p>The way the expense report is consumed is through monthly pdf attachment sent in the email to me and my partner.  So the <a href="https://github.com/psrikanthm/expense-report/blob/main/src/expense_report/reports.py#L29-L78">service layer</a> in this project constructs pdf and sends emails once a month. But one can imagine more complex use cases that demand a typical web server, and maybe even a transactional database, say we want to support user corrections of expense category.</p>

<p>You may have noticed that I skipped out Relevance Engine component in my mapping, since the data and use case I was building for is very simple it doesn’t warrant any fancy algorithms. However, I think it is a whole interesting domain on its own and I hope to build few projects that illustrate the wide space of these algorithms!!</p>

<p>Finally, here is how my expense report looks.</p>

<p><img src="/images/data-product/expense-report.png" alt="png" /></p>]]></content><author><name></name></author><category term="Data Product" /><category term="Data Architecture" /><category term="Local LLM" /><category term="Personal Finance" /><summary type="html"><![CDATA[Exploring the Data Product stack through the lens of a personal expense classifier]]></summary></entry><entry><title type="html">Analysis of text conversations with my wife</title><link href="https://psrikanthm.github.io//chat-analysis/" rel="alternate" type="text/html" title="Analysis of text conversations with my wife" /><published>2020-03-29T00:00:00+00:00</published><updated>2020-03-29T00:00:00+00:00</updated><id>https://psrikanthm.github.io//chat-analysis</id><content type="html" xml:base="https://psrikanthm.github.io//chat-analysis/"><![CDATA[<p>I was recently going through some of the earliest text conversations between me and my wife. Apart from exposing our naiveness and excitement in a cute way, I found those texts insightful of our personalities and relationship. So I tried to apply what I do for my day job in analyzing the texts data and quantify the conversations.</p>

<p>I exported the WhatsApp chat for the first two months of our relationship into text file. Since we primarily texted each other on WhatsApp this captures most of our text conversations, but doesn’t include phone calls or other media.</p>

<p>After parsing the text file and preprocessing, I looked at the quantity of the messages and the words. It seems like she sent me ~400 more messages than I did, and we overall texted ~4000 messages in two months! However, I sent those “lengthy” texts to makeup for my lesser number of messages as I used 14k words in total comared to 12k of her’s.</p>

<p><img src="/images/chat-analysis-fig1.png" alt="png" /></p>

<p>Looks like she does one word texts lot more than me <em>okay, okies, ohk, vokay, ok, k</em>, and compensates for the words with higher usage of emojis. <strong>An Emoji is worth 10 words !</strong></p>

<p><img src="/images/chat-analysis-fig2.png" alt="png" /></p>

<p>I consider myself a curious person and ask lot of questions, taking into account that these messages are from early phase of our relationship its not surprising we ask each other lots of questions. I categorized messages as questions using simple rule based filter, if the message ends with “?” or starts with “why”, “what”, “where”, “who”, “when”, “how”.</p>

<p><img src="/images/chat-analysis-fig3.png" alt="png" /></p>

<p>Apart from the core office working hours or sleeping times, we were pretty reponsive to each other. The response times shown here are in seconds and on an average my wife has faster reponse time compared to me.</p>

<p>A message is considered as conversation starter if it sent after a pre defined time window which in this case I set as 6 hours. Finally a criteria where we have almost equal number !=!.</p>

<p><img src="/images/chat-analysis-fig4.png" alt="png" /></p>

<p>Next I explored the words used in conversations, I have hidden some words and emojis for privacy reasons. Below you can see our Wordclouds, after removing standard stopwords like ‘and’, ‘or’, ‘the’ etc.</p>

<p>As expected there are plenty of words that characterize the millenial texting habits like “haha”, “hahaha”, “yup”, “soo”. It is interesting to see words from three different languages sprinkled all over, as we both are trilingual. My wife had the habit of using these weird short forms for words such as “bcz”, “ryt”, “ppl”, the habit which she shredded off lately ;)</p>

<p><img src="/images/chat-analysis-wordcloud1.png" alt="png" /></p>

<p>One thing I like in these commmon words are some hidden inside jokes and ways we used to refer each other #awww</p>

<p><img src="/images/chat-analysis-wordcloud2.png" alt="png" /></p>

<p>This text analysis would be incomplete without looking at the emojis, especially when about 2000 emojis were exchanged in 4300 messages. Clearly each of us have our favourite emojis to respond, though I was bit conservative in throwing emojis. Hey honey, stop winking so much 😉!!</p>

<p><img src="/images/chat-analysis-fig5.png" alt="png" /></p>

<p>Finally, I looked at the number of messages per day and plotted below using Plotly’s Time Series plot.  After loading the messages in Pandas’ Series indexed with timestamp, I resampled the data with a time interval of 24 hours for converting un-even spaced time series data to evenly spaced.</p>

<p>Since we had communications outside WhatsApp texts, its hard to make lot of conclusions from this plot. Though I can identify when we had our “sparks”, its also striking to see we texted each other everyday !!</p>

<p><img src="/images/chat-analysis-fig6.png" alt="png" /></p>

<p>If you would like to run this analysis on your text conversations, feel free to clone the repository <a href="https://github.com/psrikanthm/WhatChat">https://github.com/psrikanthm/WhatChat</a> and follow the instructions. There are few more stats that are available in the code which I haven’t made use of in this analysis.</p>]]></content><author><name></name></author><category term="Text Analysis" /><category term="Plotly" /><category term="WhatsApp" /><category term="Chat Analysis" /><summary type="html"><![CDATA[I was recently going through some of the earliest text conversations between me and my wife. Apart from exposing our naiveness and excitement in a cute way, I found those texts insightful of our personalities and relationship. So I tried to apply what I do for my day job in analyzing the texts data and quantify the conversations.]]></summary></entry></feed>