Friday 28 June 2024

Remote workers in Dell Technologies stand firm

It was with a fair amount of interest that I read this article during the week. Computer company Dell Technologies, like many companies in the USA, has been trying to enforce a return to office for their workers, with dismal results.



During the worldwide crisis that was COVID-19, Dell mandated a remote work policy which their employees resisted at first, but gradually grew accustomed to. And now that COVID-19 is no longer life-threatening (at least for the general public), Dell has tried an about-face on this by demanding that employees return to the office.

Sound familiar?

If you're thinking that you've heard all this before, you probably have. This is a story that has been repeated over and over since 2021, not just in the USA but all over the world. Employers like Elon Musk have even stamped their metaphorical feet and opined that chosing to work from home is "immoral", which only serves to enforce my belief that being worth millions of dollars doesn't mean you're less prone to saying stupid shit.

Going to remote work meant that many employees had made certain commitments in terms of housing location and lifestyle. A return to office would disrupt everything, and incur certain costs such as transport, food and makeup. Nice shoes. The works. Those were just financial costs; there were also costs with regard to time, such as the daily commute.

Traffic is crazy.

Can we reasonably blame workers for not wanting to go through yet another drastic lifestyle change after going all in the first time? Should we accuse them all of being lazy and unmotivated or worse, actively trying to take advantage of the company? Those bad actors do exist, but does their existence justify tarring all remote workers with the same brush? Or is that just a convenient excuse?

Also, a company known for making computers portable and providing network access to those computers so that they can be used from anywhere, is insisting on a return to office? How's that for an unsubtle dose of irony?

Standoff

What's interesting is that Dell stated that those who opted to remain remote would no longer be considered for promotion... and a full fifty percent of remote employees didn't blink. The sentiment appears to be that the threat of not being promoted was an empty one, anyway, as opportunities for promotion had been scarce even before this. Their defiance seemed to state that even if there was a genuine chance of getting promoted had they chosen to acquiesce, promotion just wasn't worth making all those changes. The carrot wasn't working.

A chance at promotion?

Of course, the stick would soon follow. Or, if you consider the aforementioned "carrot" actually more of a stick, an even bigger stick would inevitably follow. The implication being that in the case of layoffs or company restructuring, remote workers would be the first on the chopping block.

Who's winning?

Well, arguably, the fact that this story is out in the public in the first place, means that Dell as a company has lost. And if Management goes to war with its own employees over this, Dell's reputation as a workplace can only fall further.

Resentment at Dell's machinations has caused employees to not only remain remote, but also to actively seek out new employment regardless of what Dell eventually decides. There's also the sneaky feeling all around, that Dell Management knows exactly what they're doing. After all, if those opting to stay remote really do leave, this eliminates the cost of compensating them if indeed layoffs do occur. To be fair to Dell, this tactic is hardly illegal. If we're being honest, it's not even exactly original.

Dell's tactics at this point in time would seem to be directly contradictory to the stand articulated by CEO Michael Dell less than two years ago. This is an excerpt from his blog, where he waxed lyrical about Dell's inclusive work culture.
But from my experience, if you are counting on forced hours spent in a traditional office to create collaboration and provide a feeling of belonging within your organization, you’re doing it wrong.

Technology’s ability to create a do-anything-from-anywhere world, where work is an outcome rather than a place or time, also enables you to create a strong corporate culture anywhere, all the time.


And another...
Ultimately, we have committed to allow team members around the globe to choose the work style that best fits their lifestyle - whether that is remote or in an office or a blend of the two. We are redesigning office spaces for the purpose of bringing teams together for social connection and collaboration to enhance hybrid experiences.

These decisions are grounded in our culture and based on the facts of our internal data. It is a philosophy of flexibility, choice, and connection.


Companies making a u-turn from their previous stands isn't at all new, of course... but a u-turn after that extravagant song and dance about "flexibility, choice, and connection"? Unless shame is a completely foreign concept to you, that's nothing short of an utter embarrassment.

Abrupt reversal in direction.

If we give Dell the benefit of the doubt, Dell may have been all about remote work in the beginning, but perhaps that sentiment was genuine until the tides of reality hit and they realized that they simply weren't that well-equipped for it. Perhaps this was an honest mistake.

Or perhaps, more cynically, it was just corporate gaslighting. Perhaps those words by Michael Dell were merely meant to sway workers to adopt remote work due to circumstances caused by COVID-19... and now that the pandemic is over, there's no longer any need for that pretense.

All in all...

As a salaried worker and an economic digit, my heart is with the employees of Dell no matter what their ultimate decisions are. This stalemate cannot end well; eventually the pendulum will have to swing one way or another. Only time will tell if Dell Technologies, as a company, can survive.

I would like to conclude by saying that I've never been a big fan of the USA. There's precious little about American culture that I admire, that I would choose over Asian culture. But now here's one thing, at least. I appreciate the way these workers at Dell have managed to prioritize the quality of their lives over corporate promotions. That's something that we in Southeast Asia, with our obsessive hustling and pointless flexing of empty job titles, can emulate.

What the Dell?!
T___T

Monday 24 June 2024

Web Tutorial: Python Matplotlib Line Chart (Part 2/2)

It's time to render the line chart!

Begin by using the figure() method to determine how large you want the chart to be. I'm going to recommend these proportions.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))


Now, we use the plot() method for line charts. We are going to first plot goals. Remember vals carries information for both goals and appearances. We want only goals right now.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"])


The marker argument defines what the points will look like. We want a circle, so I've input "o".
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o")


Finally, for goals, I want a nice solid scarlet, and that's what we'll have for the color attribute.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))


Finally, we use the show() method to render the chart.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))

  plt.show()


I chose to show the stats for Roberto Firminho, one of the longest-serving players in the menu. Notice that the data is rendered in circular dots and it's a bright red.


Now, we do the same for appearances! Only this time, we'll use a deeper shade of red.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  plt.show()


As you can see here, the scale has changed because the maximum number of appearances is vastly greater than the number of goals. (Still a great goalscorer though!)


Let's add these values to the plot. First, for goals, we want to iterate through the values using a For loop. Since we want to use the index value as well, we will need to use enumerate() on the values.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  for index, value in enumerate(vals["goals"]):
  
  plt.show()


Then we use the text() method within the For loop. index determines the horizontal positioning, while value determines the vertical positioning. We'll add 1 to value so that the text won't overlap the dots. The next argument is the value itself, and we need to use str() on it so convert it to a string. Finally, we use the same color we did for plotting the values of the goals.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  for index, value in enumerate(vals["goals"]):
    plt.text(index, value + 1, str(value), color=(1, 0, 0))
  
  plt.show()


Looks good.


Now we do the same thing for appearances!
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  for index, value in enumerate(vals["goals"]):
    plt.text(index, value + 1, str(value), color=(1, 0, 0))

  for index, value in enumerate(vals["appearances"]):
    plt.text(index, value + 1, str(value), color=(0.5, 0, 0))

  
  plt.show()


I like the look of how things are shaping up!


Next, let's add lines to show average values. Since we have two sets of values, we want them in different colors too. Let's start with the average line for goals. We use the axhline() method for this. We begin by specifying that the y argument is the average of the goals properties in vals, using the namnean() method from numpy.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  for index, value in enumerate(vals["goals"]):
    plt.text(index, value + 1, str(value), color=(1, 0, 0))

  for index, value in enumerate(vals["appearances"]):
    plt.text(index, value + 1, str(value), color=(0.5, 0, 0))
    
  plt.axhline(y=np.nanmean(vals["goals"]))

  plt.show()


And then we make the line a dashed line by passing in "-." as the linestyle argument.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  for index, value in enumerate(vals["goals"]):
    plt.text(index, value + 1, str(value), color=(1, 0, 0))

  for index, value in enumerate(vals["appearances"]):
    plt.text(index, value + 1, str(value), color=(0.5, 0, 0))
    
  plt.axhline(y=np.nanmean(vals["goals"]), linestyle="-.")

  plt.show()


The color argument is obvious - it's the same as the plot color for goals.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  for index, value in enumerate(vals["goals"]):
    plt.text(index, value + 1, str(value), color=(1, 0, 0))

  for index, value in enumerate(vals["appearances"]):
    plt.text(index, value + 1, str(value), color=(0.5, 0, 0))
    
  plt.axhline(y=np.nanmean(vals["goals"]), linestyle="-.", color=(1, 0, 0))

  plt.show()


Nice!


We want to label it, so let's use the text() method. It starts at 0 for the horizontal position. For the vertical position, we want it just about the dashed line, so we use the same value as the dashed line, plus 5. The text will be "GOALS", and of course, we use the same color.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  for index, value in enumerate(vals["goals"]):
    plt.text(index, value + 1, str(value), color=(1, 0, 0))

  for index, value in enumerate(vals["appearances"]):
    plt.text(index, value + 1, str(value), color=(0.5, 0, 0))
    
  plt.axhline(y=np.nanmean(vals["goals"]), linestyle="-.", color=(1, 0, 0))
  plt.text(0, np.nanmean(vals["goals"]) + 5, "GOALS", color=(1, 0, 0))
  
  plt.show()


There it is.


Now, if you need me to elaborate on what you should do for appearances, I'll be terribly disappointed.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  for index, value in enumerate(vals["goals"]):
    plt.text(index, value + 1, str(value), color=(1, 0, 0))

  for index, value in enumerate(vals["appearances"]):
    plt.text(index, value + 1, str(value), color=(0.5, 0, 0))
    
  plt.axhline(y=np.nanmean(vals["goals"]), linestyle="-.", color=(1, 0, 0))
  plt.text(0, np.nanmean(vals["goals"]) + 5, "GOALS", color=(1, 0, 0))
  
  plt.axhline(y=np.nanmean(vals["appearances"]), linestyle="-.", color=(0.5, 0, 0))
  plt.text(0, np.nanmean(vals["appearances"]) + 5, "APPEARANCES", color=(0.5, 0, 0))

  
  plt.show()


We'll be done soon. The hard parts are over.


Now all that's left to do is use xlabel() and title() methods for your chart!
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  for index, value in enumerate(vals["goals"]):
    plt.text(index, value + 1, str(value), color=(1, 0, 0))

  for index, value in enumerate(vals["appearances"]):
    plt.text(index, value + 1, str(value), color=(0.5, 0, 0))
    
  plt.axhline(y=np.nanmean(vals["goals"]), linestyle="-.", color=(1, 0, 0))
  plt.text(0, np.nanmean(vals["goals"]) + 5, "GOALS", color=(1, 0, 0))
  
  plt.axhline(y=np.nanmean(vals["appearances"]), linestyle="-.", color=(0.5, 0, 0))
  plt.text(0, np.nanmean(vals["appearances"]) + 5, "APPEARANCES", color=(0.5, 0, 0))
  
  plt.xlabel("Seasons")
  plt.title("Liverpool FC Player Stats for " + player)

  plt.show()


Let's try it with Salah this time! Uh-oh, looks like the top label overlaps! This can happen if the stats for appearances are fairly consistent, putting the average value near the maximum.


So what we need to do is add this line, to set the highest limit to the max value for appearances, plus a few. This ought to be safe, because, barring some freak statistic, the number of appearances for any player over the course of a season should be significantly higher than the number of goals. Unless it's the kind of player who makes maybe five appearances in one season but scores a hat-trick each time. But this is too much of an anomaly for me to bother with right now.
def lineChart(labels, vals, player):
  plt.figure(figsize = (10, 5))

  plt.ylim(0, max(vals["appearances"]) + 10)
  plt.plot(labels, vals["goals"], marker="o", color=(1, 0, 0))
  plt.plot(labels, vals["appearances"], marker="o", color=(0.5, 0, 0))
  
  for index, value in enumerate(vals["goals"]):
    plt.text(index, value + 1, str(value), color=(1, 0, 0))

  for index, value in enumerate(vals["appearances"]):
    plt.text(index, value + 1, str(value), color=(0.5, 0, 0))
    
  plt.axhline(y=np.nanmean(vals["goals"]), linestyle="-.", color=(1, 0, 0))
  plt.text(0, np.nanmean(vals["goals"]) + 5, "GOALS", color=(1, 0, 0))
  
  plt.axhline(y=np.nanmean(vals["appearances"]), linestyle="-.", color=(0.5, 0, 0))
  plt.text(0, np.nanmean(vals["appearances"]) + 5, "APPEARANCES", color=(0.5, 0, 0))
  
  plt.xlabel("Seasons")
  plt.title("Liverpool FC Player Stats for " + player)
  plt.show()


Here we go.


Well, that was fun...

It's not all that often I do a web tutorial that doesn't involve coding for the web. But when I do, it's really rewarding. The matplotlib library seems pretty basic so far, but it has tons of features I'm itching to explore.

And that's the bottom line!
T___T

Friday 21 June 2024

Web Tutorial: Python Matplotlib Line Chart (Part 1/2)

Last month, we wrangled a bar chart in Python. This month, we will repeat the process, this time with a line chart.

Now, the considerations for a line chart are different from a bar chart. Line charts are primarily utilized to display progress over time. Therefore, the line chart will show both goals and appearances for particular players, over the course of several seasons.

There are some things we can reuse from the code, such as the dictionary from which we derive the data, and the helper function seasonName(). We will clear out the contents of the barChart() function, and rename it to lineChart(). This time, the function will not have season or stat as a parameter, because the line chart will show both goals and appearances across all seasons. We will, however, need the parameter player.

As for the rest of the code, we will discard it.
import numpy as np
import matplotlib.pyplot as plt

def lineChart(labels, vals, player):
  
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}
  },
  2019: {
    "Mohamed Salah": {"goals": 23, "appearances": 48},
    "Roberto Firminho": {"goals": 12, "appearances": 52},
    "Sadio Mane": {"goals": 22, "appearances": 47},
    "Alex Oxlade-Chamberlain": {"goals": 8, "appearances": 43}
  },
  2020: {
    "Mohamed Salah": {"goals": 31, "appearances": 51},
    "Roberto Firminho": {"goals": 9, "appearances": 48},
    "Sadio Mane": {"goals": 16, "appearances": 48},
    "Alex Oxlade-Chamberlain": {"goals": 1, "appearances": 17},
    "Diogo Jota": {"goals": 13, "appearances": 30}
  },
  2021: {
    "Mohamed Salah": {"goals": 31, "appearances": 51},
    "Roberto Firminho": {"goals": 11, "appearances": 35},
    "Sadio Mane": {"goals": 23, "appearances": 51},
    "Alex Oxlade-Chamberlain": {"goals": 3, "appearances": 29},
    "Diogo Jota": {"goals": 21, "appearances": 55},
    "Luis Diaz": {"goals": 6, "appearances": 26}
  },
  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}
  }
}


We will require a variable, ans. That's the only input we will need.
  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}
  }
}
  
ans = ""


Therefore, when we create the While loop, this time we only need to do it as long as ans is not 0.
  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}
  }
}
  
ans = ""

while (ans != 0):


We will still need to have a menu. This time, we will have a menu of players. Create an empty list, players.
  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 = []
  
ans = ""

while (ans != 0):


Then iterate through the data object. For every element, grab the list of players by using the keys() method, and add it to players.
players = []
for season in data:
  players = players + list(data[season].keys())

  
ans = ""


Now, since players reappear in different seasons, we are going to have duplicates in the list players, and we'll need to remove them. If we create a dictionary from the keys of players. this automatically eliminates all duplicates (dictionaries cannot have duplicates).
players = []
for season in data:
  players = players + list(data[season].keys())
  
dict.fromkeys(players)
  
ans = ""


Then we put the result back in a list using the list() function...
players = []
for season in data:
  players = players + list(data[season].keys())
  
list(dict.fromkeys(players))
  
ans = ""


... and assign the value back to players. So now the list players has all unique values.
players = []
for season in data:
  players = players + list(data[season].keys())
  
players = list(dict.fromkeys(players))
  
ans = ""


And then use the sort() method on players.
players = []
for season in data:
  players = players + list(data[season].keys())
  
players = list(dict.fromkeys(players))
players.sort()
  
ans = ""


Now within the While loop, we iterate through the players list using a For loop. Since we want the index value, we will need to use the enumerate() function on players.
players = list(dict.fromkeys(players))
players.sort()
  
ans = ""

while (ans != 0):
  for i, p in enumerate(players):


Thrn we'll print out the options as we iterate.
while (ans != 0):
  for i, p in enumerate(players):
    print (str(i + 1) + ": " + p)


At the end of it, the option we print out is 0, to exit.
while (ans != 0):
  seasons = []
  
  for i, p in enumerate(players):
    print (str(i + 1) + ": " + p)
    
  print ("0: Exit")


And here, we ask the user to select from the list, using an input() function, and assign the value to ans.
while (ans != 0):
  seasons = []
  
  for i, p in enumerate(players):
    print (str(i + 1) + ": " + p)
    
  print ("0: Exit")

  ans = input("Select a player")


Before running the code, comment out the lineChart() function definition. We haven't written anything for it, so running the program will trigger an error.
#def lineChart(labels, vals, player):


There you go! A menu of players, nicely sorted in alphabetical order.


As before, we only want an integer value, so use the int() function here to force-convert the resultant value.
ans = int(input("Select a player"))


There's a possibility that the user will enter something non-numeric, so we encapsulate the entire thing in an infinite While loop and a Try-catch block.
while True:
  try:

    ans = int(input("Select a player"))
    break
  except:
    print("Invalid option. Please try again.")


After that, outside of the infinite While loop, we set the program to exit using the break statement if ans is 0. If the value of ans is numerical but invalid, we "restart" the While loop using the continue statement.
while True:
  try:
    ans = int(input("Select a player"))
    break
  except:
    print("Invalid option. Please try again.")  

if (ans == 0): break
if (ans > len(players) or ans < 0): continue


Let's try entering "hello". Obviously, it triggers the prompt ""Invalid option. Please try again."


Then we try a negative number. This "restarts" the While loop.


Then we try "0", and this quits the program altogether by exiting the outer While loop.


Now that we have a value, it's time to get other data. First, let's declare seasons as a list, within the first While loop.
while (ans != 0):
  seasons = []
  
  for i, p in enumerate(players):
    print (str(i + 1) + ": " + p)


Next, we define player by using the value of ans(minus 1 because counting starts from zero) as a reference to the players list. This will get you the name of the player chosen.
while (ans != 0):
  seasons = []
  
  for i, p in enumerate(players):
    print (str(i + 1) + ": " + p)
    
  print ("0: Exit")

  while True:
    try:
      ans = int(input("Select a player"))
      break
    except:
      print("Invalid option. Please try again.")  

  if (ans == 0): break
  if (ans > len(players) or ans < 0): continue
    
  player = players[ans - 1]


Then we declare stats as a dictionary object with goals and appearances as properties, both set to empty lists.
if (ans == 0): break
if (ans > len(players) or ans < 0): continue
  
player = players[ans - 1]
stats = { "goals": [], "appearances": []}


Now iterate through data using a For loop.
player = players[ans - 1]
stats = { "goals": [], "appearances": []}

for season in data:


We only want to operate on data that features the selected player. Thus we use an If block to check if player is in the list returned by running the keys() method on that current element in data.
for season in data:
  if player in data[season].keys():


If so, we append that particular season to seasons. But we'll want the proper season name instead, and that is where the seasonName() function comes in.
for season in data:
  if player in data[season].keys():
    seasons.append(seasonName(season))


We then add to stats. We want to append to the goals and appearances lists. Thus, if Luis Diaz was selected, the seasons 2021 and 2022 would be added to seasons. Then his goals and appearances for 2021 and 2022 would be appended to the goals and appearances properties of stats.
for season in data:
  if player in data[season].keys():
    seasons.append(seasonName(season))
    stats["goals"].append(data[season][player]["goals"])
    stats["appearances"].append(data[season][player]["appearances"])




Remember commenting out the call to lineChart()? Well, time to undo that action. We will next call lineChart() and use seasons, stats and player. seasons and player will be used to set labels and stats will provide values.
for season in data:
  if player in data[season].keys():
    seasons.append(seasonName(season))
    stats["goals"].append(data[season][player]["goals"])
    stats["appearances"].append(data[season][player]["appearances"])

lineChart(seasons, stats, player)


Next

Rendering the line chart with two sets of values.

Sunday 16 June 2024

Why Mastery of Programming is a Myth

Much has been made about mastering the craft, and the constant moaning that developers these days are sloppy and don't make the grade.

Some developers feel that way about themselves. They feel that they aren't masters of their craft (spoiler: many of them aren't) and this disqualifies them from being where they are right now. That they will be found out someday. That they should be grateful to be employed. I'm no stranger to that feeling, especially during my younger years.

On some level, I get it; we all want a certain standard of professionalism in the workplace. And not adhering strictly to standards, results in bug-ridden, subpar software. I've even said before in a previous blogpost that sometimes, taking the trouble to do the simple things right, is vital.

When does this become silly?

There's a limit to how much gatekeeping can take place before it becomes ridiculous.

The saying goes like this: If something's worth doing, it's worth doing well. Now, this isn't exactly untrue. If you're going to put in the effort to do something, you may as well try to do it properly.

However, here's something to consider. Trying to do something properly isn't the same thing as actually doing it well. Ever heard people butcher your favorite song in a karaoke session? Do you think they're singing badly on purpose?

Singing badly.

Let me counter the saying with this - before you learn to do something well, you first have to learn to do it half-assed. Kind of like learning to walk before you can learn to run.

Now, do programmers write bad code? Of course they do. It's not always due to the lack of giving a shit. Sometimes they just never figured out a better way. Even with the will to continuously improve, there are a million things a programmer can learn, all in different directions, how to code better.

Are we saying that programmers who don't currently write great beautiful watertight code, shouldn't have a job? And how, pray tell, are these programmers ever going to get their feet wet if they don't get to apply their code in the real world? Not all code sends people to Mars, or handles billions of dollars in financial transactions hourly. Some code just handles penny-ante stuff like displaying blog posts on a web page, and if these programmers want to do that job, why shouldn't they? Unless you're saying that you want that job?

The insistence on "Mastery"

Sometimes, the insistence on "Mastery" in programming is just ego talking. Not mastery. Masturbation. The mental kind.

You know those job ads that want their candidates to have a "mastery" or "best-in-class" or "first-rate" of whatever discipline? They're kidding themselves. Whatever project they think is going to change the world, chances are, it's just marketing talk and that role doesn't actually need anyone that good. They just want to feel like they're getting the best, even though common sense would tell them that the best would be already happily employed somewhere else. The best wouldn't likely be applying to their stupid job applications.

We only want
the best!

I mean, can you imagine the sheer level of delusion it would take to even imagine that your fabulous earth-shattering idea (which probably has already been done to death) requires that amount of expertise?

Or even the famous phrase "go big or go home", a favorite phrase among the ambitious. Really? So if you can't be the best, or even "go big", you're just going to pack up and leave? If you can't be a master of your craft, you're just going to stop embarrassing yourself by even trying to improve? That's not being a serial winner, that's just being a baby. So go suck on a pacifier, Junior, adults are working here.

I should also mention that in just the field of programming alone, there are way too many things to learn and get good at. Just statistically, what you learn tends to be what you need, at that moment. What you get good at, tends to be what you keep doing over and over. If you needed to use Python to, for example, clean up string values before committing them to a database, you would end up learning a lot of string manipulation functions, the gotchas of character encoding, and all that jazz. You certainly would not be learning about Python's web-scraping or chart-plotting utilities. Would that mean you didn't know Python at all, just because you didn't know everything about it? That's a depressingly binary way of looking at things.

"Mastery"? This is a joke, right?

And on the other side...

...we have programmers getting Imposter Syndrome and wondering if they're good enough to deserve their current position. Newsflash: where you are right now, is the product of not just your technical abilities, but also how much you impressed during interviews and how well you networked with the right people. Something about you made up for whatever you think you lack. Ergo, you pretty much deserve to be wherever you are. This Imposter Syndrome nonsense suggests that you think working as a programmer is only about programming. And that, my friend, is one the the first and biggest mistakes a programmer can make.

If you're going to make a living at coding, the first fact that you need to reconcile with, is that your first few attempts are going to be laughable. Your first hundred websites, your first hundred web forms, your first few web apps. They will be, despite your darnedest efforts to the contrary, horribly inefficient, a nightmare to maintain and result in a mountain of technical debt. Does that mean you should listen to be would-be gatekeepers of our sacred profession and never start until you have become a master?

Absurd.

Why stop there? Let's take this nonsense even further.

Unless you can do more than boil eggs and make sandwiches, unless you can do shit that would win you the approval of Gordon Ramsay himself, you shouldn't step in the kitchen.

Can't flaunt your figure
unless it's perfect?

Unless you're in great shape and the sight of your figure in tights isn't visually offensive, you should dress modestly.

Unless you can type in full sentences and perfect English and have full mastery of the language, you should stay off Social Media. (I would actually support that one)

This is why you get software developers who are scared to death to admit when they made a mistake, or that they don't know something. This harms the industry a whole lot more than having an entire bunch of supposed "masters" of their craft out there. If you can't admit that you don't know something, if your environment is full of factors that discourage that kind of disclosure, that adds self-created roadblocks to learning. And without learning, there is no growth.

Conclusion

Everything is a work in progress. You're a work in progress. Forget that at your own peril.

Why insist on perfection in an imperfect world? Exercise some humility; you're probably not destined for greatness. And unless you're willing to start small, you almost definitely never will be.

Master of none,
T___T

Monday 10 June 2024

When an absence of value doesn't equate to a NULL

In programming, it can be easy to confuse the NULL value some something similar, such as undefined or (with regard to string literals) empty. This is because people tend to classify values into two categories - either something has value, or it has no value.

But NULL is a value in itself... and that value is explicitly no value. Confusing? I can see how it would be.

NULL values vs undefined

Let's say I gave you a voting slip in an election, where you could choose only one candidate out of two choices.

Not voting is not the same
as voting for no one.

Did you choose the first, or second candidate? That's the value of your choice. If your choice was, say, a variable named choice, and the candidates were numbered 0 or 1, the value of choice would either be 0 or 1.

But what if you decided not to vote, or hadn't had a chance to vote yet? Then, if this was JavaScript, the default value for choice would be undefined, because a value has not yet been assigned to choice.

What if you decided to invalidate your vote by spoiling it or just not filling it in? Then you would have voted. A value would be assigned to choice. And since you invalidated the vote, that means you voted for neither. But your vote was still registered. Whereas undefined means that you hadn't voted yet, in this case you had voted and you had explicitly not voted for either candidate. This would be more akin to a NULL value; you explicitly choose not to make a choice. But that in itself is a choice, therefore the variable choice now has a value. And that value, of course, is no value. Or NULL.

NULL values vs empty strings

Similarly an empty string is not the same as a NULL. Both an empty string and a NULL have values. The NULL has, as before, a value of no value. And the empty string is simply a string with no characters, but that does not change the fact that it is a string.

A string without characters
is still a string, not a NULL.

Try running a string operation on an empty string...
var str = "";
console.log(str.length) //gives you 0.


...and on a NULL.
var str = null;
console.log(str.length) //gives you an error.


Trying to get the length property from str in the first example will give you a 0, because it is a string with zero characters. In the second example, you get an error because a NULL is not a string object, and therefore does not have a length property.

To expand on the above, a NULL is not only not a string, it is also not an integer, or a float, or a Boolean. A NULL is a NULL.

The Takeaway

The takeaway from this, of course, is that NULL values are NULL values... not empty strings or 0s. NULL values are values that basically mean "no value", which is not the same as actually having no value.

It's NULL or never,
T___T


Thursday 6 June 2024

Is it really ScarJo, and why does it matter?

If you're both a fan of Hollywood and tech, the tail end of May must have been eventful for you. A-list movie star Scarlett Johansson took legal action against tech company OpenAI for using her voice without permission, for promotion of their product. Just to elaborate, in case there's even the remotest chance that you don't know who or what OpenAI is, it's the company that is responsible for generative technology such as ChatGPT, Dall-E and Sora.

What happened was that OpenAI included Voice Mode to ChatGPT, where the user can choose a voice out of a few presented options, to engage with ChatGPT.

Voice Mode.

The sticking point was that one of the voices, named "Sky", sounded an awful lot like Scarlett Johansson herself. In the days that followed, ScarJo revealed that OpenAI's CEO, Sam Altman, had approached her twice for permission to use her voice for that purpose, and she had declined to give it. It sounded like OpenAI had gone ahead anyway, disregarding her objections, when they launched the new feature. Johanssen, herself no stranger to suing corporations when necessary, did not hesitate to call out Open AI.

Was it ScarJo, though?

Apparently not! OpenAI apologized to Johansson for the confusion and clarified that the voice wasn't hers, but belonged to a voice actor whom they hired for the job. She simply sounded an awful lot like Johansson, and had her mannerisms.

Nevertheless, OpenAI took down the voice "out of respect" but the bonfire that was the controversy continues to rage on. Though it begs the question: if OpenAI are completely innocent of the charge, why is there a need to do that?

A.I generated
image of ScarJo.

I can't verify if the voice is indeed Scarlett Johansson's, or simply sounds similar. I couldn't pick Johansson voice out of a lineup for the simple reason that I'm not great with voices. Also (heh heh) I spend significantly more time looking at rather than listening to her.

But perhaps that's not the important point in this entire thing. If A.I can legally replicate any celebrity's likeness for the purpose of promoting any product, service or agenda regardless of that celebrity's intentions, that entails a couple of implications.

The first, and arguably less important, is financial. Now, I'm not concerned with ScarJo specifically. She'll be fine; this woman makes more money in a month than most of us do in a year. However, just being able to ride on the back of someone else's popularity to promote whatever you want without needing to pay that person a cent, strikes me as fundamentally and ethically wrong.

The more alarming implication

The second implication, is, of course, having someone else use your image to sell something you don't endorse. In an age where deepfakes and misinformation abounds, this is a huge, glaring red flag.

Beware!

Take the case of one Gina Carano. Back in 2021, she found herself being cancelled due to some remarks she made on Social Media. Whether one agrees with her or not, the fact remains is that she aired those views. She stood behind them, and suffered the consequences. There is no debate about that.

But what if she didn't espouse those views? What if she only appeared to do so because someone made an A.I deepfake of her spouting those views? Now we're wandering into some truly sinister territory.

Sam Altman himself posted a Tweet on X, leading up to the release of the new feature, referencing the movie Her, which, quite ironically, starred Scarlett Johansson herself as an A.I generated voice, thus implying that the voice of Sky in ChatGPT's Voice Mode was really ScarJo. Now, he could have been completely innocent of the charge of stealing ScarJo's voice for OpenAI. The voice could really have, as claimed, come from a completely unrelated voice actress. With that stunt from him, it no longer matters. The existing fears that people have towards generative A.I could only have been amplified after this debacle, regardless of the legal outcome.

Seriously, between Sam Altman and Elon Musk, what is it with tech company CEOs doing stupid shit and acting like consequences are for lesser mortals?

Also, if anyone feels like arguing that it's not too far-fetched for Scarlett Johansson to endorse OpenAI, do remember that this woman is in the business of filmmaking, an industry that OpenAI's Sora may well one day disrupt. Call it a failure of imagination on my part, but I can't see her (or any other actor) championing that.

In the Final Analysis

It doesn't matter one iota if Sky was really ScarJo. Perception is everything, especially in a world where A.I is generating content.

Whether or not OpenAI survives this legal battle; indeed, whether this even results in a legal battle at all, its public image has taken a serious battering.

Looks like Sky is the limit!
T___T