Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Thursday, 30 July 2026

A Software Developer's Vacation In Singapore: Coast-to-Coast Trail Edition (Part 4/4)

In the weeks that followed, I would constantly revisit these sub-routes. I would do them again, and in reverse. The point was to get myself familiar with the entire route. Prepare myself for the day I attempted to the entire thing in one go.

And then the day came. I scheduled it, right on Good Friday, 3rd of April, and prayed it wouldn't rain.

In the morning, I had a McDonald's breakfast at Jurong East Mall, then headed off to Chinese Garden MRT where I prepped two bottles of water and a sandwich, and a Snickers bar from the Cheers store. Minimal prep, I know. That's me - minimal to a fault.

There, the Anywheel bike was rented, and I started off from Checkpoint One. Things were smooth-going much of the way; I was pretty familiar with the route by now, after having gone back and forth over it obsessively.

This time, I rode on the other side of Bukit Timah Road just for a more varied experience. This took me right past the Rochor River, and this was surprisingly beautiful. I might have caught it at a good time.

Rochor River was
quite the eyeful.

It was just before 12 noon when I arrived at Checkpoint Four. So far so good. I was feeling fine, and barely even breathing hard.

The First Break

I sat down at the Kheam Hock Park to eat my sandwich. The sky was overcast and I was concerned it might rain and cockblock my entire day. It was maybe ten minutes past noon.

Kheam Hock Park, right at the
end of a boardwalk.

I hadn't had time to really take a look at the tiny park. Charming. Good for a dog walk.

Once I reached the overhead bridge at Lornie Highway, I got over the bridge, passed MacRitchie just like the last time, and audibly groaned when I arrived at the construction going on at Bishan and Ang Mo Kio. Damn, I really hate this part.

Once I got that part behind me, the best part of the journey began - starting from Ang Mo Kio Linear Park to. the entire Waterway stretch at Punggol. I took some time to settle in and eat my Snickers bar.

Huddling under this bridge
for my Snickers break.

Once done, I was on my way again, passing Checkpoint Ten at Coney Island without bothering to stop. The sun was blazing but I soldiered on out of the beautiful cycling paths and onto the hellish landscape of Punggol Tingkat.

By this time, my ass was killing me and my right leg had gone numb. I was conscious of a light but persistent burning sensation on the back of my neck. When I finally crossed from Punggol Barat back to the mainland and was greeted by the sight of the entrance to Rower's Bay Park, it was like water in a desert.

Rower's Bay Park entrance.

The sun was high in the sky, and it was making the water sparkle. The beauty of the sight was indescribable. I'd just completed the Coast-to-Coast Trail, and it was glorious.

The alternative route from Checkpoint Seven

On a separate occasion, just out of curiosity, I returned to Checkpoint Seven at Sengkang Swimming Complex, to explore the alternate route to Checkpoint Ten. This was actually the Sengkang Floating Wetland, a patch of swamp that was somehow eerily beautiful in the day. I had to dismount from the bicycle and push.

The bridge over the wetland.

Nice park after the wetland.

The route led on to Sengkang Riverside Park, which I largely skirted to embark on a long stretch of road leading west, parallel to the Tampines Expressway.

Road leading out of the park
and under a flyover.

Long stretch ahead
next to TPE.

After some riding, I arrived at the Seletar Aerospace Flyover, and headed north again along Seletar Aerospace Way, right up until I came to this idyllic green patch called The Oval. 

Very nicely manicured
infrastructure at The Oval.

I was pretty much winging it at this point, with a rough idea as to which direction to my destination, but no real clue as to the paths I needed to travel to get there.

That final stretch!

Aerospace greenery. Pretty fly!

The next twenty minutes saw me meander around the aerospace until I discovered Seletar West Link, after which I simply rode the green stretch all the way to the welcoming entrance of Rower's Bay Park.

Thoughts on this route

Now this was underwhelming, and I suspect this was because I really didn't know the official route. In fact, part of the problem might be that this part of the Coast-to-Coast Trail simply wasn't meant for cycling. It was akin to trying to find semicolons in a Python code base. Sure, you'll probably be able to find them, but they won't mean the same thing to you especially if you cut your teeth on Java.


I don't feel like revisiting this part anytime soon, though the other parks around the area do look promising for exploration.

Epilogue

This little foray into cycling in Singapore, would soon expand into a more extended exploration of similar trails in Singapore. The fact that I had also quit smoking at this point, also meant that I was able to spend more time in these parks.

This was a side of Singapore I hadn't appreciated up to now. And I intend to remedy that.

Where there's a wheel, there's a way!
T___T

Friday, 13 March 2026

Named Versus Positional Notation in Functions and Methods

For as long as I have used functions and methods in programming, the default mode for me has been Positional Notation. Now, one may ask, what's Positional Notation?

Positional Notation is the method in which a function in a programming language identifies its parameters - by the positional order in which they are introduced. This example is in PHP.
function x($a, $b)
{
    //code here
}


In this call to the function x(), the value of a is 100 and the value of b is "abc". This is because the value 100 appears first, as does the parameter a in the function specification for x(). And the value "abc" appears second, as does the parameter of b.
$y = x(100, "abc");


Years back, I picked up Python and found out about Named Notation. This is a function declaration in Python.
def x (a, b):
    //code here


This is the way it's called in Positional Notation. No surprises there.
y = x (100, "abc")


And this is the way it's called in Named Notation! Note that in Named Notation, the argument values are directly assigned to the parameter names.
y = x (a = 100, b = "abc")


Or this. Because in Named Notation, the positional order no longer matters!
y = x (b = "abc", a = 100)


Why I love this...

Named Notation trumps Positional Notation. I'm sorry for fans of the latter; it's not even close.

Getting the order of arguments wrong has introduced many a bug in the code. In the case of a language like PHP, this is made even easier because PHP's parameter positions are notoriously (and annoyingly) inconsistent. Check out the definitions for strstr(), str_replace() and in_array(). The absence of Named Notation, thankfully, appears to have been fixed in PHP 8.0.

Position no longer matters.

Another thing about not needing to adhere to Positional Notation, is that if the programmer decides to add parameters to the function definition later, this makes it easier to do without breaking any existing implementations. In other words, this is great for extensibility.

...and why I don't

For Named Notation to work, a programmer needs to know the names of the parameters of the function or method they're attempting to call. This is often found in the documentation. A badly spelled parameter name could ruin your day.

And in the case of functions with many parameters, this could get cumbersome. On the other hand, Positional Notation is worse if there are many parameters. Far, far worse.

The only, solitary one!

Specifying the name of a parameter could be superfluous if there is only one parameter. After all, if there is only one parameter and only one argument is passed in, it makes sense that the argument is meant for that parameter.

Conclusion

Named Notation is not new, but for some reason or other, it just has not been the default in passing arguments into functions the same way Positional Notation has. It's starting to change, with support for Named Notation taking hold in recent times. This can only be a good thing. I'm digging it.

And that's my position!
T___T

Wednesday, 4 February 2026

No Blanket Solutions

There exists in the spoken language, a supreme contradiction. A sentence that manages to be simultaneously profound in its humility and stunning in its arrogance.

"If I can do it, anyone should be able to do it." - every douchebag ever.


Why is this humble? Well, on the surface, it seems like people who say this are modestly describing their achievements as mediocre. They feel that they're not special, so if someone as unexceptional as them can do something, there's no reason why most other people wouldn't be able to do it. For example, I quit smoking almost two years ago. I did it far easier than many people did, with no lingering physical or psychological side-effects despite having been a pack-a-day guy for the better part of twenty years. No discipline was required; the struggle was non-existent for me. However, I'm also reasonably certain that this isn't that uncommon. There are plenty of people, wired the same way as me, who could accomplish that. Acknowledging that you're not special, is humility.

Why is this arrogant? At the same time,  if I were to claim that I quit smoking without much fuss, and there is something wrong with people who can't simply follow my example; that's a problematic stance to take. Assuming that oneself is the standard on which everyone else should be based around, is both immature and pretty egoistical. It lacks self-awareness.

And both positions are wrong.

We're not all the same.

They're wrong for the very uncomplicated reason that we're all built different. We don't all come off some assembly line with the exact same characteristics. There are significant variances across data points like culture, upbringing, physical and environmental.

For example, people who spent their formative years in a Southeast Asian multicultural country like Singapore or Malaysia would have significantly less trouble picking up a third or fourth language as opposed to, say, an Englishman born and bred in the UK. Incidentally, that's why people coo and act impressed when a white guy speaks Mandarin (even if his pronounciation is dogshit). Whereas nobody bats an eyelid when an Asian guy speaks English well. Why? Because a white dude even attempting to speak anything other than dodgy English is impressive, whereas for an Asian guy it's just another Tuesday.

Is it fair? Well, of course not; but if we're going to acknowledge that everyone's built different, then nothing is fair.

The tech space

"There are no blanket solutions" is a favorite refrain of mine, because it's true especially in the tech space. You can have all the best practices in the world, but they have to be evaluated against the exact context in which you're applying them. The concept of best practices is a good thing, don't get me wrong, but only if not applied blindly.

Frameworks aren't always the way to go; I've said this before over and over. They're often the way to go in software development, but not in every situation.

Not every data storage solution has to include a database.

Not every 2FA solution
looks the same.

Not every 2FA solution looks the same - some involve texts to mobile phone and some involve a third-party authenticator app.

You wouldn't use Python to code every damn thing, just the same way you wouldn't use Java to do it. At least, I hope not.

While we're at it, almost every organization's implementation of Agile Methodology looks different from the next.

But while all I've said so far is uncontroversial, that's for the tech space. The software development context.

In a personal context

Someone once told me he wanted to be like me. I had a place of my own. I was doing well financially with no debt. While I wasn't filthy rich, I spent money without needing to think too hard about it. And above all, I was chill. I didn't let what I didn't have, bother me too much.

And all that was before I got blissfully married and really started stepping up my game at life.

Possibly, to an outsider who was just watching me live my life, it looked like I had everything without needing to turn to drugs or alcohol in order to cope. Furthermore, it looked like all that was needed to achieve what I achieved, was to live my life the way I did. Sadly, I had to disabuse this person of that fantasy. He was not going to achieve the same things I did by living like me, simply because he wasn't me.

There's more than
one path to the
rainbow.

My achievements aren't spectacular by any means. An apartment, a job and a spouse. Spare cash in the bank. The means to take care of my immediate family. These things are achievable by the vast majority of people. All I've really done is earn as much money as I can while living a very modest lifestyle. And that's the hard part.

You see, not everyone can, or wants to, live life the way I do, as if I were still drawing an income of just under SGD 3,000 a month. It takes a specific kind of person to happily sacrifice pleasure for stability. Not everyone has the same experience and needs. Not everyone has the same personality. I know plenty of people who would struggle mightily with a simple existence.

Overseas vacations, wine and cheese, cab rides, expensive pets, gym and club memberships - I won't miss any of those things because they're not the kind of things a guy like me cares about. But again, not everyone is like me in this regard. And honestly, for the sake of Singapore's economy, I really hope not. (Part of the reason why I can live the way I do, is because other people have this habit of buying pricey shit they don't need. So keep on doing what you do, guys; you're awesome.)

Therefore, it's not a simple matter of glibly saying "live life this way". Not everyone will take to my chosen lifestyle the way I have, or reap the exact same rewards from doing so. Why should anyone travel the same path and have the same outcomes as me when they're fundamentally different from me? That makes no sense, does it?

In a nutshell...

The mistake most people make is to assume that everyone wants the same things that they do, or at everyone is built the same way. That's why you have well-intentioned but severely misguided people going around advising others to have kids or go to Church or become a vegetarian because those things have benefitted them... and they assume that these things will benefit other people in exactly the same way.

People can certainly achieve the same things I have, or more... but they're going to have to find their own pathway to it.

Stay unique,
T___T

Tuesday, 25 November 2025

Tech skillsets: Go deep or go wide? (Part 1/2)

"Jack of all trades, master of none" is a term I've heard all too often when fellow techies describe my stint as a web developer. Back then, I was a generalist dipping my sticky fingers into every new and shiny tech I encountered. In a sense, I'm still that web developer. Just with more money.

Go deep or go wide?

I want to explore the pros and cons of generalizing as opposed to specializing, especially in the context of today's tech landscape. The industry needs both specialists and generalists. That's a simple fact that shouldn't need some nobody tech blogger pointing out, but sometimes people get overly emotional about defending their stance. Want to specialize? Cool, do that. Want to generalize? Also cool, go crazy. But we need to be cognizant of the tradeoffs.

The case for specializing over generalizing

Before every tech and his dog started calling themselves "full-stack developers", specialists seemed to earn a whole lot of money. That was back when I was reading job ads asking for "deep expertise" and extensive work experience in more narrow scopes such as back-end programming and database administration. Some areas, such as COBOL programming, pay a lot for the simple reason that COBOL is still largely in use but COBOL programmers appear to be a dying breed. I've touched a lot of programming languages, but just enough to qualify as a hobbyist in each one. I have a decent frame for comparison between them - a Python lecturer of mine was actually quite entertained at my comparisons of the language to PHP and Ruby - but that often isn't very useful in the professional sense.

Specialists earn big.

The companies whose budgets are large enough to afford to pay for specialists, also tend to use these specialists for projects that are grand in scale. Projects that actually require deep expertise. Projects that add incalculable value to one's CV.

Deep knowledge is also useful for judging the extent of one's resolve and commitment. After all, it takes plenty of both to spend the effort. I personally know someone who specializes in HTML and CSS. It's a very narrow niche and perhaps not very profitable, but undeniably impressive.

Generalists - quite unfairly, I might add - seem to have developed a reputation for being easily distracted. Lacking the necessary discipline and focus required to specialize. I would actually say it takes an extraordinary amount of discipline to avoid going too deeply down one rabbit hole and shutting out everything else, but that's not the general sentiment. Therefore, in some circles, generalists are seen as those who were unable to specialize as opposed to specialists being unable to generalize.

The conventional advice when it comes to tech specialization, is to specialize in one area while simultaneously being decent in a few related areas. For instance, if you're a Data Analytics guru, you may want to specialize in statistical analysis and mathematics, but at the same time get reasonably good with Data Visualization tools and learn a bit of Python. If you're a primarily a Front-end Developer, learning some simple database concepts would be a useful addition to HTML, CSS and JavaScript. Maybe pick up a few frameworks such as ReactJS or VueJS, just to round things out.

I won't argue against that advice, but these days it feels like nothing is ever enough.

Next

We explore the opposite case!

Monday, 28 July 2025

Do techies lean Liberal or Conservative? (Part 1/2)

Since the day I became aware of the Culture Wars over in the good old US of A, there's been a nagging question in the back of my mind. What side do techies really stand on? I'd like to think that we aren't a slave to any ideology - especially coming from Singapore, we're a pragmatic people who believe in whatever works in the most efficient way possible.

Silicon Valley does appear largely Liberal, or at least, that's what they would have us think. Jeff Atwood of Coding Horror, founder of Stack Overflow, actually jumped onto the anti-Trump bandwagon. However, after the recent electoral victory by the Republicans on the 5th of November last year, Big Tech has quite handily changed their tune. As I've often suspected, that's not the tech part speaking; that's the business part. That the appearance of Liberal domination in Silicon Valley was a veneer, and ran no deeper than that.

Nothing deeper than that!

And thus the question was begged. The answer, of course, that software developers, like many people, identify with both. They have some positions that are Liberal, and some that are Conservative.

The funny thing is that I identified three positions that could be considered both Liberal or Conservative, depending on one's perspective.

How Software Developers are Liberal

Identity does not matter. Software development is a field where logic reigns supreme. At some levels, it does not even require a particularly high I.Q, just the ability to do basic math and thick logically. Gender, race and sexual orientation don't determine an individual's ability to do math, and as such, are irrelevant when it comes to the ability to code, test and deploy software. A software developer is not going to automatically think that another software developer is competent based on identity.

Software
development isn't
just mens' work.

In fact, one of the most baffling (and frankly insulting) things I have heard as a developer is being told that I should be better at the job because I'm a man. What, and my years of hard work count for nothing? I'm naturally supposed to be better at it because God made boys engineers by default? Well, that's not what this tech thinks, and any male tech who tells himself that kind of rubbish, does himself (or anyone) no favors.

Innovation versus tradition. Don't get me wrong, we do have our traditions. But technology itself is a result of innovation and experimentation; daring to try new things. Thus, you will never hear a software developer saying that we should do something this way just because that's how we've always done it. That kind of argument holds no water for us. It runs counter to everything that drives a techie.

Tradition has its
charm.

We may respect tradition, but we are not slaves to it. There are no sacred cows. Tradition has its place, but tradition should also know its place.

We have tech innovations today precisely because we dared to go against tradition. We dared to challenge the pre-existing ideas of what was possible, or acceptable. What separates us from the herd is the willingness to ruthlessly drop tried and tested methods in favor of demonstrably better methods.

The collective versus the individual. Skillset-wise, you will never see a more diverse group. That's because in the tech landscape where there are very frequent changes and new things to learn, people end up learning across different (but often related) tech disciplines or going deeper into existing ones.

Tech skillsets come in all
shapes, sizes and colors.

There are no unifying standards as to what makes a techie, though there's been no shortage of would-be gatekeepers trying to keep things neatly classified. It's a lost cause.

Take any two developers, even from a broad collection of, say, back-end developers, and you may end up with one mostly trained in Java and one trained in Python. Take your two developers from a less broad group of Python developers, and you might get one who's more of a web scraper and one who's more of a data analyst.

Yes, tech itself is a huge domain, and evolving as I write this. Consequently, its practitioners are similarly varied.

Next

How we're Conservative.

Wednesday, 23 July 2025

JavaScript now has negative indexing... sort of

Searching arrays in JavaScript (or any language, really) can be a tricky proposition. One of my favorite ways to search for a value within an array in JavaScript, is the indexOf() method.

With this method, I just need to invoke it while passing in the string to search for, as an argument.
var arr = ["This", "is", "a", "test", "array"];
var index = arr.indexOf("test"); // 3

But what if I wanted it the other way round? What if I wanted to find out what value was the, say, third element in an array? Well, simple enough.
var arr = ["This", "is", "a", "test", "array"];
var val = arr[2]; // "a"

Now, what if I wanted the second last element in the array?

In Ruby, one could do this. This is known as negative indexing.
arr = Array["This", "is", "a", "test", "array"]
val = arr[-2] // "test"

In JavaScript, this doesn't fly. Get the length of the array, less 2, then use it as the index.
var arr = ["This", "is", "a", "test", "array"];
var val = arr[arr.length - 2]; // "test"

There is a way to use negative indexing in JavaScript, though. Use the slice() method.
var arr = ["This", "is", "a", "test", "array"];
var val = arr[-2, -2]; // ["test"]

The end result is an array, though. So there's one more step to take before you can access the value.
var arr = ["This", "is", "a", "test", "array"];
var val = arr[-2, -2]; // ["test"]
val = val[0]; // "test"


So, those are the things you have to do, in order to get the value of an element n times from the end... or do you? Now with the at() method, you literally just need to do this.
var arr = ["This", "is", "a", "test", "array"];
var val = arr.at(-2); // "test"

What is this sorcery?!

The at() method was introduced as recently as 2023. With it, a programmer can do in JavaScript what one can do in Python and Ruby - use negative indexes. Is that really so important considering that functionally, we already had all we needed in the slice() method?

slice(), slice(), baby.

Well, the slice() method complicates things. Sure, it's a handy method that can be used for big or small cases, but for the case we're talking about, it's complete overkill. Using the at() method makes code a lot more readable, and the importance of that cannot be understated.

Conclusion

It's always fun to see how programming languages evolve. Would have been nice for me to discover the at() method sooner than years after it emerged, but this only goes to underscore the fact that things change so fast in this industry and there's often something new to learn.

["Thanks", "for", "reading", "and", "see", "you", "again!"],
T___T

Friday, 11 October 2024

TeochewThunder: Year Ten (Part 1/2)

Ten big ones. Ten!

This blog has been around for ten years since October in 2014. It feels like a momentous occasion, only it's really not, because I wasn't working up to this being any kind of milestone. In all honesty, I just kind of did my thing, rinse and repeat, and before I knew it, it was the tail end of 2024.

Ten glorious years!

So what's been going on? As I speak, numerous wars rage across the planet, and some of them even involve actual physical violence. Artificial Intelligence is being hyped to the heavens the same way Blockchain and the Metaverse were. The USA is being all dramatic about their upcoming Presidential Election.

In other words, same shit, different day. Business as usual, folks!

This blog has kept moving along even with the numerous changes in my life since it first started in 2014. It hasn't been a huge success but it doesn't need to be. Maintaining it has kept me sharp, sharper than if I'd spent my time playing video games and scrolling on TikTok.

Blogging has been slow this year. From the October of last year, I concentrated on moving house, and finally completed the move on the last day of 2023. Since then, it's been a period of settling in. In addition to that, my Lenovo died and I was scrambling for a replacement.

Web Tutorials

This year, I broke away from my bread-and-butter of HTML, CSS and JavaScript, and started making web tutorials in Python. Mostly I just didn't want to neglect the skills I picked up in 2021 and allow them to atrophy too much. So if you regularly check in to see what other cool stuff I have that you can jump into right away without needing to install an environment for it... tough luck I guess.

Been doing a bit more
of Python lately.

Along the way, though, I found time to do something in ReactJS. And the result was last month's web tutorial.

My work has begun incorporating Generative AI for minor improvements. Nothing earth-shattering. Just trying to develop that muscle memory in the event that one day I'll really need it. And also so that on the occasions I write about AI, I'll actually have some idea what I'm talking about.

Since I'm on the subject of web tutorials, some readers would have noticed that my Easter web tutorial was posted after Easter this year, as opposed to before. This was a total accident as I lost track. But come to think of it, why can't I do this? There's no good reason, for example, for a Valentine web tutorial to always be before Valentine's Day. Sometimes the occurence of Valentine's Day and Chinese New Year in the same month, makes this a logistical challenge. Thus, from this year forth, I'm giving myself the liberty of posting such web tutorials after the day.

Tech News

As mentioned earlier, AI has been dominating my newsfeed regularly. The other tech news regular, unfortunately, is big tech layoffs. Just seems to be a thing these days. Thankfully I'm not in big tech. I'm a tech practitioner in the food and beverage industry. Not one of the high-earners to be honest, but I don't have a problem with staying employed. As of last week, I've been at the company four years now.

Getting laid off.

Elon Musk also appears on my feed from time to time for something stupid. It gets less interesting each time, thus I'm saving my limited supply of fucks for something more... fuck-worthy, I guess.

There's been plenty of news related to work or tech (sometimes both!) on the home front. Some were neither, to be fair, even though I managed to relate it to my professional experiences - such as with Singapore's Smoking Samsui Woman Controversy Through a Web Developer's Eyes.

All in all, I think I've gotten better about identifying what qualifies as tech news, or at least tying it in by viewing it through a tech lens.

Other Stuff

My ongoing quest to remove irrelevant tags in this blog is progressing well. In the space of a few months, I've combed through the tags from A to Z. Some of the tags I removed were Donald Trump and Hillary Clinton, because clickbaity as they can be, they're not tech personalities and that's what I want to keep this blog about. Arguably I should have started way before now, but ah well, hindsight and all.

I've started included more personal stuff on here. Much of my personal stuff is tied into my identity as a software dev, either directly or indirectly, so that's great. A lot of my life realizations were gleaned from writing software.

I'm chill now. Swear to
fuckin' God.

This has been ongoing for a couple years now, but I'm proud to report that my efforts to decrease the amount of profanity that I use on this blog, is paying off. At least, it has for this year. What's really needed is to sustain the effort for another year, and another, until it becomes second nature. The last thing I want is to come off as this perpetually raging old man.

I've improved on my ability to use AI to generate images for this blog. It's been a year, and you'll probably see that the images I've generated are far less plastic. I hope. Plus, it's free. Since I'm not making a damn cent off this blog, I think it's fair.

Next

Greatest hits of 2024.

Wednesday, 11 September 2024

The Slip Knot Analogy

If you have ventured into knot-tying in any capacity at all, you may have encountered this term: slip knot. Not the band Slipknot, which is a different animal altogether.

Slip knots are knots that have their finished ends doubled back for an easy and quick release. Almost every other knot, especially hitches, can be effectively turned into slip knots. Here's a few YouTube tutorials from WhyKnot.

Slip Reef Knot



Slip Clove Hitch



Slip Sheetbend



In each and every one of these cases, just tugging on the end releases the knot quickly. Definitely quicker and less messy than untying the knot in the old fashioned way, especially if it's been cinched on tight.

What do slipknots remind me of in programming?

Why, the good old Guard Clause, of course. That's when you put an If block right at the start of a function, method or subroutine, to exit early if certain conditions are met, so that no further time needs to be spent processing the request.

Here's an example in Python.
def average(lst):
    if len(list) === 0: return null

    total = 0

    for (item in lst):
         total += item

    return total / len(lst)


And here be the Guard Clauses.
def average(lst):
    if len(lst) === 0: return null
    if len(lst) === 1: return lst[0]


    total = 0

    for (item in lst):
         total += item

    return total / len(lst)


Visually they look nothing like knots, slip knots or otherwise. But functionally? Almost a perfect match.

The Guard Clauses, when triggered, enable a quick and early release from the procedure. The same way tugging on the doubled end of a slip knot releases the knot quickly.

Forget me knot,
T___T

Saturday, 17 August 2024

Web Tutorial: Python Matplotlib Heatmap (Part 2/2)

Here comes the fun - or at least, the visual - part. We work on the heatMap() function.

We start off by setting the size of the plot.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))


Then we feed in the values, vals, to the lmshow() method. It's a two-dimensional list, remember.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)


Finish off with a call to the show() method, which displays the heatmap.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)

  plt.show()


This is the default view. You can't make much sense of it just yet, because the labels aren't clear.


Now, use the xticks() method. If we use numpy's arange() method and pass in 0 and the length of players, we'll get an array of values from 0 to the length of players, minus 1. Pass this into the call to xticks(). This will change nothing, because that's already the default setting.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)
  plt.xticks(np.arange(0, len(players)))

  plt.show()


But if you pass in players as an argument after that...
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)
  plt.xticks(np.arange(0, len(players)), players)

  plt.show()


...you see the x-axis is populated with values! However, the names can be a little long (looking right at you, Alex Oxlade-Chamberlain!) so let's modify the code a bit.


Add a rotation value as an argument.
plt.xticks(np.arange(0, len(players)), players, rotation=90)


Looking good!


We do something similar for the yticks() method, passing in seasons for the labels.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.show()


And now our heatmap takes shape. We have labels, now we just need to know what those colors mean.


Let's use the colorbar() method to facilitate this...
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.colorbar()

  plt.show()


Now you can see, with the bar on the right appearing, that yellow is a higher number and deep blue is 0.


Let's go for a better color palette. It's Liverpool, so pass in "Reds" as the cmap argument.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals, cmap="Reds")
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.colorbar()

  plt.show()


Now you can see the color scheme has changed, along with the color bar!


Let's do something simple and add a title using the title() method and stat parameter value.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals, cmap="Reds")
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.colorbar()

  plt.title("Liverpool FC Player " + stat)
  plt.show()


There it is... a small piece of the puzzle but adds so much clarity.


Next, we will add labels to the colored squares. It's quite straightforward - first we need a nested For loop top traverse through the two-dimensional list that is vals. For both the inner and outer For loop, we need the index value, thus we will have to use enumerate(), first on vals, then on each list within vals.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals, cmap="Reds")
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.colorbar()
  
  for colindex, lst in enumerate(vals):
    for rowindex, val in enumerate(lst):


  plt.title("Liverpool FC Player " + stat)
  plt.show()


Then in the inner loop, we use the index values (rowindex and colindex) and the value itself, val, in the text() method.
for colindex, lst in enumerate(vals):
  for rowindex, val in enumerate(lst):
    plt.text(rowindex, colindex, val)



Pretty straightforward.... though you may have noticed that the default color is black and that Mohamed Salah's goal talley for the 2017/2018 season here is almost invisible due to the lack of color contrast.


Let's fix that! Just before the nested For loop, declare vals_avg and set the value using the nanmean() method, passing in vals as an argument. Thus we obtain the average value of the entire dataset in the heatmap.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals, cmap="Reds")
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.colorbar()
  
  vals_avg = np.nanmean(vals)
  for colindex, lst in enumerate(vals):
    for rowindex, val in enumerate(lst):
      plt.text(rowindex, colindex, val)

  plt.title("Liverpool FC Player " + stat)
  plt.show()


Then in the inner For loop, declare rgb as 0. If the value of val is greater than vals_avg, set it to 1. So now we have a situation where, if val is below average, rgb is 0, and otherwise it is 1.
vals_avg = np.nanmean(vals)
for colindex, lst in enumerate(vals):
  for rowindex, val in enumerate(lst):
    rgb = 0
    if (val > vals_avg): rgb = 1

    plt.text(rowindex, colindex, val)


And then we modify the call to the text() method to include a color argument.
vals_avg = np.nanmean(vals)
for colindex, lst in enumerate(vals):
  for rowindex, val in enumerate(lst):
    rgb = 0
    if (val > vals_avg): rgb = 1
    plt.text(rowindex, colindex, val, color=())


And we will use the value of rgb in that argument! This means that we will have white if val is above average, and black if below.
vals_avg = np.nanmean(vals)
for colindex, lst in enumerate(vals):
  for rowindex, val in enumerate(lst):
    rgb = 0
    if (val > vals_avg): rgb = 1
    plt.text(rowindex, colindex, val, color=(rgb, rgb, rgb))


See what we've done? Now in cases where the square color is dark (which means the value is above average), the text is white. And in cases where the square color is white or light (which means the value is below average), the text is black!


Well, this was fun...

Heatmaps are a nice visualization tool provided you have a somewhat even spread of number of possible values along two dimensions.

Warm regards,
T___T

Wednesday, 14 August 2024

Web Tutorial: Python Matplotlib Heatmap (Part 1/2)

How's everyone doing?

After all we've been through with the Liverpool Football Club dataset, here's one more!

Today we will get to making a heatmap in Python's matplotlib library. It's rather like a line chart in the sense that we plot data over two axes. The difference is that the data will be a two-dimensional list. Thus, while the data gathering is very straightforward - we ask the user if they want to see goals or appearances, and that's it! - the data massaging is a little more complex.

Let's start by taking the code for the line chart, and renaming the lineChart() function to heatMap(). Instead of labels as an argument, we will have two arguments in place of labels - seasons and players. Clear everything else except for the seasonName() function; we will still need that. Needless to say, we'll need the data as well.
import numpy as np
import matplotlib.pyplot as plt

def heatMap(seasons, players, vals, stat):
  
def seasonName(year):
  return str(year) + "/" + str(year + 1)

data = {
  2017: {
    "Mohamed Salah": {"goals": 44, "appearances": 52},
    "Roberto Firminho": {"goals": 27, "appearances": 54},
    "Sadio Mane": {"goals": 20, "appearances": 44},
    "Alex Oxlade-Chamberlain": {"goals": 5, "appearances": 42}
  },
  2018: {
    "Mohamed Salah": {"goals": 27, "appearances": 52},
    "Roberto Firminho": {"goals": 16, "appearances": 48},
    "Sadio Mane": {"goals": 26, "appearances": 50},
    "Alex Oxlade-Chamberlain": {"goals": 0, "appearances": 2}
  },


After the data portion, clear everything; we'll start relatively fresh. Declare two lists - players and seasons. These will be used as arguments in the heatMap() function later.
  2022: {
    "Mohamed Salah": {"goals": 30, "appearances": 51},
    "Roberto Firminho": {"goals": 13, "appearances": 35},
    "Alex Oxlade-Chamberlain": {"goals": 1, "appearances": 13},
    "Diogo Jota": {"goals": 7, "appearances": 28},
    "Luis Diaz": {"goals": 5, "appearances": 21}
  }
}

players = []
seasons = []


Use a For loop. For every element in data, use seasonName() to derive the season name and then append to seasons. At the conclusion of the loop, you should have a full list of seasons.
players = []
seasons = []

for season in data:
  seasons.append(seasonName(season))


Also, derive a list of players using the list() function and using the keys() method on the current element of data. It's possible for us to just add the entire list to players using the addition operator.
for season in data:
  seasons.append(seasonName(season))
  players = players + list(data[season].keys())


Now we'll have a full list of players as well in players, but many of them will be repeats. What we'll do is use the fromkeys() method of the Python's dict object to convert list into a dictionary (removing all duplicates in the process), then use the list() function to convert back to a list. And finally, we'll sort players using the sort() method.
for season in data:
  seasons.append(seasonName(season))
  players = players + list(data[season].keys())
  
players = list(dict.fromkeys(players))
players.sort()


We follow up by declaring ans as an empty string, then creating a While loop that runs as long as ans is not 0.
players = list(dict.fromkeys(players))
players.sort()
  
ans = ""

while (ans != 0):


In the loop, print the options - "1" for goals, "2" for appearances, and "0" to exit.
ans = ""

while (ans != 0):
  print ("1: goals")
  print ("2: appearances")
  print ("0: Exit")


In here, create an infinite While loop.
while (ans != 0):
  print ("1: goals")
  print ("2: appearances")
  print ("0: Exit")

  while True:


Inside this While loop, we have a Try-catch block.
while True:
  try:

  except:


Here, we'll use the input() function to ask the user for input, then use the int() function to convert the input to an integer. If the user enters a letter instead, this will trigger an exception, which we will then handle with the print() function.
while True:
  try:
    int(input("Select a stat"))
  except:
    print("Invalid option. Please try again.")


Now, assign the value of the input to ans, and then use a break statement. This means that if an exception is not triggered by an invalid input, the user will break out of the infinite loop.
while True:
  try:
    ans = int(input("Select a stat"))
    break
  except:
    print("Invalid option. Please try again.")


Then, depending on the value of ans, assign a value to stat. But if ans is 0, we stop all execution. If ans is not 0, 1 or 2, we restart the loop.
while (ans != 0):
  print ("1: goals")
  print ("2: appearances")
  print ("0: Exit")

  while True:
    try:
      ans = int(input("Select a stat"))
      break
    except:
      print("Invalid option. Please try again.")
  
  if (ans == 1): stat = "goals"
  if (ans == 2): stat = "appearances"
  if (ans == 0): break
  if (ans > 2 or ans < 0): continue


As you can see, I entered "hello" and it was rejected, then I entered "5", and "-1", both of which resulted in being asked the question again. It was only after I entered a "0" that the script terminated.


We will need a two-dimensional list, stats. And then we're going to populate stats using a nested For loop. We first iterate through the seasons. Now we can't use the list seasons for this because it contains the proper season names instead of the keys in the dataset. So we'll need to use a list of the keys in data. In the inner loop, we iterate through players.
if (ans == 1): stat = "goals"
if (ans == 2): stat = "appearances"
if (ans == 0): break
if (ans > 2 or ans < 0): continue
  
stats = []

for season in list(data.keys()):
  for player in players:


In the outer loop, we declare an empty list, arr. Then in the inner loop, we use an If block to check if player is in the element of data pointed to by season.
stats = []

for season in list(data.keys()):
  arr = []
  for player in players:
    if (player in data[season]):

    else:


If so, we append the relevant stat to arr. If not, we simply append a zero.
for season in list(data.keys()):
  arr = []
  for player in players:
    if (player in data[season]):
      arr.append(data[season][player][stat])
    else:
      arr.append(0)      


And finally, in the outer loop, we append arr to stats. By the end of this, stats should be a two-dimensional list of stats for each player by season, with 0s where the player was not present during the season (or simply made no appearances or scored no goals).
for season in list(data.keys()):
  arr = []
  for player in players:
    if (player in data[season]):
      arr.append(data[season][player][stat])
    else:
      arr.append(0)
      
  stats.append(arr)


And then we call the heatMap() function, passing in seasons and players for the labels, then stats as the values, and stat as the string variable that we'll use in the title.
for season in list(data.keys()):
  arr = []
  for player in players:
    if (player in data[season]):
      arr.append(data[season][player][stat])
    else:
      arr.append(0)
      
  stats.append(arr)

heatMap(seasons, players, stats, stat)


Next

We implement a heatmap using the data we have obtained!

Sunday, 21 July 2024

Web Tutorial: Python Matplotlib Pie Chart

Howdy, folks!

Now that we're on a roll with the Python matplotlib bar and line charts, we should absolutely go ahead and make a pie chart. This one is going to be simple because most of the work - data gathering and massaging - has already been done with the bar chart. And with matplotlib, the tough calculations are taken care of! Therefore, this web tutorial will not be a two-parter.

Just take the code for the bar chart here, and begin by clearing out the contents of the barChart() function. Then rename the function to pieChart(). Also, we won't be using numpy, so remove the line that imports that library.
#import numpy as np
import matplotlib.pyplot as plt

def pieChart(labels, vals, season, stat):


Also, change the call to the function. With these small changes done, we're all set to create a pie chart!
stats = [];
for v in values:
  stats.append(v[stat])

pieChart(players, stats, season, stat)


We begin by using the figure() method to set the size of the chart. For this, I recommend a square value, meaning the width is the same as the height.
def pieChart(labels, vals, season, stat):
  plt.figure(figsize = (8, 8))


We then use the pie() method. In it, we pass vals, then pass labels as the labels argument.
def pieChart(labels, vals, season, stat):
  plt.figure(figsize = (8, 8))
  
  plt.pie(vals, labels=labels)


We then use the title() method to display the stat type and season. You'll notice that we still use the custom function seasonName() that we created for the bar chart.
def pieChart(labels, vals, season, stat):
  plt.figure(figsize = (8, 8))
  
  plt.pie(vals, labels=labels)

  plt.title("Liverpool FC Player " + stat + " for " + seasonName(season))


Finally, of course, we use the show() method to display the chart.
def pieChart(labels, vals, season, stat):
  plt.figure(figsize = (8, 8))
  
  plt.pie(vals, labels=labels)

  plt.title("Liverpool FC Player " + stat + " for " + seasonName(season))
  plt.show()


Now we run the program. I select the 2021/2022 season and select appearances as the stat type. We get a colorful pie chart!


Here's something potentially useful - a legend!
def pieChart(labels, vals, season, stat):
  plt.figure(figsize = (8, 8))
  
  #customcolors = [];
  #customexplode = [];

  plt.pie(vals, labels=labels)
  plt.legend()

  plt.title("Liverpool FC Player " + stat + " for " + seasonName(season))
  plt.show()


This is for when you have a lot of colors, and you want an easy way to refer without having to squint at the labels.


However, we're not going to use it because I have something else entirely in mind. So, remove this line.
def pieChart(labels, vals, season, stat):
  plt.figure(figsize = (8, 8))

  plt.pie(vals, labels=labels)
  #plt.legend()

  plt.title("Liverpool FC Player " + stat + " for " + seasonName(season))
  plt.show()


Let's put percentages into the wedges. The autopct argument is how we format the string to display percentage labels.
plt.pie(vals, labels=labels, autopct="%1.1f%%")


Neat!


Next up, let me show you why I didn't want the legend earlier. That's because, this being a Liverpool FC chart, I want it in shades of red. And if everything's a shade of red, the legend is going to be next to useless. Start by declaring the list, customcolors. Follow up with a For loop that iterates through vals. Since we want the index value, use enumerate() on vals.
def pieChart(labels, vals, season, stat):
  plt.figure(figsize = (8, 8))
  
  customcolors = [];

  for index, value in enumerate(vals):
      
  plt.pie(vals, labels=labels, autopct="%1.1f%%")

  plt.title("Liverpool FC Player " + stat + " for " + seasonName(season))
  plt.show()


For each element, append stuff in customcolors. We want a Hex string that represents the a shade of the color red. So the string is going to be "#" followed by 6 characters. The last 4 are always "0000".
def pieChart(labels, vals, season, stat):
  plt.figure(figsize = (8, 8))
  
  customcolors = [];

  for index, value in enumerate(vals):
    customcolors.append("#" + + "0000")
      
  plt.pie(vals, labels=labels, autopct="%1.1f%%")

  plt.title("Liverpool FC Player " + stat + " for " + seasonName(season))
  plt.show()


And as for the missing 2 characters, we want them to be any Hex value from "96" up to "FF". So, use index with a formula that gets progressively bigger with each iteration. The lowest possible number is 150 (which is 96 in Hexadecimal). To ensure that it's always a two-character Hex value, run the result in the format() function, passing in "02x" as the second argument.
def pieChart(labels, vals, season, stat):
  plt.figure(figsize = (8, 8))
  
  customcolors = [];

  for index, value in enumerate(vals):
    customcolors.append("#" + format(index * 20 + 150, "02x") + "0000")
      
  plt.pie(vals, labels=labels, autopct="%1.1f%%")

  plt.title("Liverpool FC Player " + stat + " for " + seasonName(season))
  plt.show()


At the end of this, customcolors should be a list full of Hex values of the color red! Add it to the pie() function as the argument colors.
plt.pie(vals, labels=labels, colors=customcolors, autopct="%1.1f%%")


I've deliberately selected a season with a lot of values to let you see what it looks like. The colors are disparate enough that they look distinct when right next to each other, but again, useless as part of a legend. Also, be warned, this is not going to work if you have more than a handful of values!


The next thing you might want to do is cut out a wedge of that pie. Let's say we want the biggest contributor to the pie to stand out further. What do we do? Why, we pass in a list with float values that will tell Python how much we want each wedge to be separated from the rest - not at all (a 0) or a little (any value greater than 0 but less than 1). To do that, we need another list, customexplode.
def pieChart(labels, vals, season, stat):
  plt.figure(figsize = (8, 8))
  
  customcolors = [];
  customexplode = [];

  for index, value in enumerate(vals):
    customcolors.append("#" + format(index * 20 + 150, "02x") + "0000")
      
  plt.pie(vals, labels=labels, colors=customcolors, autopct="%1.1f%%")

  plt.title("Liverpool FC Player " + stat + " for " + seasonName(season))
  plt.show()


In the For loop, check if value is equal to the highest possible value in vals. If so, add 0.1 to the list. If not, add 0 to the list.
for index, value in enumerate(vals):
  customcolors.append("#" + format(index * 20 + 150, "02x") + "0000")
  
  if (value == max(vals)):
    customexplode.append(0.1)
  else:
    customexplode.append(0)


Then put customexplode into the pie() function as the argument explode.
for index, value in enumerate(vals):
  customcolors.append("#" + format(index * 20 + 150, "02x") + "0000")
  
  if (value == max(vals)):
    customexplode.append(0.1)
  else:
    customexplode.append(0)
    
plt.pie(vals, labels=labels, colors=customcolors, explode=customexplode, autopct="%1.1f%%"")


And this is what happens!



That was short and sweet...

As mentioned at the start, most of the work had already been done in previous web tutorials. The only thing left was really to use the data to render the pie chart.

Goodpie for now,
T___T