[{"data":1,"prerenderedAt":607},["ShallowReactive",2],{"navigation":3,"index":22,"resume-jobs":43,"index-blogs":78,"mdc--vsasgt-key":579,"mdc-30pbux-key":591},[4],{"title":5,"path":6,"stem":7,"children":8,"page":21},"Blog","/blog","blog",[9,15],{"title":10,"path":11,"stem":12,"children":13},"Does spending more time on a chess move increase its quality?","/blog/chess-games","blog/chess-games/index",[14],{"title":10,"path":11,"stem":12},{"title":16,"path":17,"stem":18,"children":19},"Building custom HTML reports using Qlik Automate","/blog/html-automation","blog/html-automation/index",[20],{"title":16,"path":17,"stem":18},false,{"id":23,"title":24,"about":25,"blog":28,"body":31,"description":32,"experience":33,"extension":35,"hero":31,"meta":36,"navigation":37,"path":38,"seo":39,"stem":41,"__hash__":42},"index/index.yml","Jens Korzilius",{"title":26,"description":27},"About Me","Welcome to my portfolio! I'm based in Rotterdam, with a focus on providing data clarity to clients in the insurance and mortgage industry.",{"title":29,"description":30},"Latest Articles"," ",null,"Analytically minded, intrinsically motivated. Eager to meet you!",{"title":34},"Work Experience","yml",{},true,"/",{"title":40,"description":27},"Jens Korzilius - BI/Data Analyst","index","0N_2NpvMDm4100jch24MNg3WYbFp75eEVnxfv9mGB4k",{"id":44,"extension":35,"jobs":45,"meta":75,"stem":76,"__hash__":77},"resumeJobs/resume/jobs.yml",[46,56,66],{"title":47,"date":48,"company":49,"bulletpoints":55},"Junior BI / Data Consultant","Jul 2025 - Present",{"name":50,"logo":51,"url":52,"color":53,"location":54},"HippoLine","custom:hippoline","https://hippoline.nl","#70b32c","Moordrecht","- Specialised in the data needs of the medical, mortgage, and insurance industries\n- Delivered full-stack BI solutions on top of CRMs, ERPs, and other data sources\n- Projects include building custom data pipelines, warehouses, dashboards, and reporting\n- Independently managed a diverse client portfolio, handling technical implementation and commercial activities such as writing quotes,\n  following up on sales leads, and managing shareholder expectations\n",{"title":57,"date":58,"company":59,"bulletpoints":65},"Receptionist / Waiter","Jan 2023 - May 2025",{"name":60,"logo":61,"url":62,"color":63,"location":64},"Bonheur Horeca Group","i-material-symbols:room-service-outline-sharp","https://bonheurhorecagroep.nl/","#efb512","Tilburg","- Responsible for the phone lines of all 10+ locations overnight\n- Completed regular first aid and fire safety training\n- First point of contact, caring for guests in a professional and welcoming manner\n",{"title":67,"date":68,"company":69,"bulletpoints":74},"Administrative Account Manager","Jun 2020 - Jan 2023",{"name":70,"logo":71,"url":72,"color":73,"location":64},"Considium","i-material-symbols:health-cross-rounded","https://www.considium.nl","#06aed7","- Architected and operated administrative systems to securely process highly sensitive customer documents\n- Maintained and initiated relationships with existing and potential clients\n- Operated in accordance with ISO & government guidelines\n",{},"resume/jobs","NPoFwaTT9iGIqd2qYe15gX_7-i96KQUf9X0gPqiNemk",[79,190],{"id":80,"title":16,"body":81,"date":183,"description":184,"draft":21,"extension":185,"image":186,"meta":187,"minRead":31,"navigation":37,"path":17,"seo":188,"stem":18,"__hash__":189},"blog/blog/html-automation/index.md",{"type":82,"value":83,"toc":176},"minimark",[84,89,109,133,140,144,147,155,158,163,166,169,173],[85,86,88],"h2",{"id":87},"background","Background",[90,91,92,93,102,103,108],"p",{},"At ",[94,95,50],"a",{"href":96,"rel":97,"target":101},"https://hippoline.nl/",[98,99,100],"nofollow","noopener","noreferrer","_blank",", we use ",[94,104,107],{"href":105,"rel":106,"target":101},"https://www.sentinelone.com/",[98,99,100],"SentinelOne"," to detect software on our computers with known vulnerabilities or exploits in them, in order to keep everything as secure as possible. While this is a very important and often time sensitive process, our current setup does not allow us to alert employees when new vulnerabilities are found on their laptops. A colleague has to export, filter, and send out vulnerability reports manually, which makes it not feasible to do this daily. This blog describes how i automated this process, saving hours of work every month, and making our entire company more ISO compliant, and more importantly, more protected against cyber attacks.",[90,110,111,112,117,118,123,124,128,129,132],{},"Since we are a ",[94,113,116],{"href":114,"rel":115,"target":101},"https://www.qlik.com/us",[98,99,100],"Qlik"," based company, I researched the options for sending out daily & weekly reports using ",[94,119,122],{"href":120,"rel":121,"target":101},"https://www.qlik.com/us/da/products/qlik-automate",[98,99,100],"Qlik Automate",". It has a built in Reporting connector, which ",[125,126,127],"em",{},"sounds"," like the right tool for the job. But in reality, it's way too limited; tables are put into a pdf as if they're screenshots, meaning that if there are more rows than fit on a single page, those rows are cut off. There is also currently no way to change the styling or layout, and while aesthetics weren't a priority, this still impacted the usefulness negatively. With my affinity for website building, I noticed that ",[94,130,116],{"href":114,"rel":131,"target":101},[98,99,100]," offers both HTML and PixelPerfect capabilities. While this seemed like a better fit, support was lacking for conditional formatting and email sending logic.",[90,134,135,136,139],{},"So I came up with my own solution, using ",[94,137,122],{"href":120,"rel":138,"target":101},[98,99,100]," to conditionally build up a HTML page using variables, which would go directly into the body of an email; no attachment or link required.",[85,141,143],{"id":142},"implementation","Implementation",[90,145,146],{},"Qlik Automations work and look like a flowchart diagram, using blocks to gather data directly from Qlik apps, which you can then process and apply logic to in order to perform certain tasks. The nice thing about this is that you can make selections as if you're inside your Qlik dashboard, which means you only have to model the data once inside the app.",[90,148,149,150,154],{},"The main idea of the automation is to append rows of vulnerabilities and information about them to variables, which can then be inserted into standard variables containing things like the ",[151,152,153],"code",{},"\u003Chtml>"," tag and the headers, which will be the same between reports. This allows full control over the styling, as we can use regular CSS, as well as the logic of which vulnerabilities show up where.",[90,156,157],{},"To start, I compare the date of the most recent report to today, to make sure we never send out an email about the same report multiple times.",[159,160],"expand-image",{"alt":161,"src":162},"Step 1","/blog/html-automation/Step_1.png",[90,164,165],{},"Now we begin the first of the main loops, which ensures we send a separate, personalized report for each employee. At the start of each iteration, the variables holding our HTML are emptied, so they can be filled again. Because vulnerabilities are appended, if we didn't do this, every employee would receive their own vulnerabilities, plus the ones of the employees before them.",[90,167,168],{},"Throughout this automation you will see output blocks. These aren't functionally necessary, but help with troubleshooting and monitoring that the automation is doing what we expect.",[159,170],{"alt":171,"src":172},"Step 2","/blog/html-automation/Step_2.png",[90,174,175],{},"Some employees have multiple laptops, for example due to recently transitioning to a new one, so we split the vulnerabilities per device. Counts are extracted to show how many vulnerabilities exist at a quick glance.",{"title":177,"searchDepth":178,"depth":178,"links":179},"",3,[180,182],{"id":87,"depth":181,"text":88},2,{"id":142,"depth":181,"text":143},"2026-05-22","Sometimes, when all out-of-the-box solutions fall short, you have to come up with your own.\n\nRead here how I solved a reporting problem using tools beyond what they were originally meant for.\n","md","/blog/html-automation/cover.jpg",{},{"title":16,"description":184},"Cw4D86eIpkiT2Nw9DsVtkWJoXv666wfpdt3C1VPF8C8",{"id":191,"title":10,"body":192,"date":573,"description":574,"draft":21,"extension":185,"image":575,"meta":576,"minRead":31,"navigation":37,"path":11,"seo":577,"stem":12,"__hash__":578},"blog/blog/chess-games/index.md",{"type":82,"value":193,"toc":565},[194,198,219,234,257,266,275,284,289,344,352,356,359,364,369,372,375,378,381,387,391,394,397,400,404,407,411,414,417,420,423,427,430,434,438,441,451,466,472,480,488,501,505,512,522,525,545,548,551,554,556,559,561],[85,195,197],{"id":196},"dataset-domain-understanding","Dataset & Domain Understanding",[90,199,200,201,206,207,212,213,218],{},"Analysis will be done using Python 3.12. Specifically, the ",[94,202,205],{"href":203,"rel":204,"target":101},"https://pandas.pydata.org/",[98,99,100],"Pandas"," and NumPy packages for analysis and data cleaning, along with Matplotlib and Seaborn for visualizations.\nThese are the tools I started with when learning data analysis, although I'm very interested in trying out alternatives like ",[94,208,211],{"href":209,"rel":210,"target":101},"https://pola.rs/",[98,99,100],"Polars"," or ",[94,214,217],{"href":215,"rel":216,"target":101},"https://plotly.com/product-tour/",[98,99,100],"Plotly",", which I plan to write an article about in the near future.",[90,220,221,222,227,228,233],{},"The dataset comes from ",[94,223,226],{"href":224,"rel":225,"target":101},"https://web.chessdigits.com/data",[98,99,100],"Chessdigits.com",", who scraped and cleaned 200.000 online games. They were played on the ",[94,229,232],{"href":230,"rel":231,"target":101},"https://lichess.org",[98,99,100],"Lichess"," website, the second most popular place to play chess on the internet, in 2019. Each row represents one game. The two most important columns for this analysis are:",[235,236,237,249],"ul",{},[238,239,240,244,245,248],"li",{},[241,242,243],"strong",{},"Eval_ply_x:"," the computer evaluation of the position ",[125,246,247],{},"after"," the last played move",[238,250,251,254,255,248],{},[241,252,253],{},"Clock_ply_x:"," the player's remaining time ",[125,256,247],{},[258,259,260],"blockquote",{},[90,261,262,265],{},[125,263,264],{},"'ply'"," in chess means half of a move: so when white starts, their move is ply 1, black's response is ply 2, white's next move is ply 3, and so on.",[90,267,268,269,274],{},"Both of these columns go up to 200 ply, but most games will have a lot of missing values towards the end of that range.\nThe computer evaluation is based on the best move according to a chess playing computer like ",[94,270,273],{"href":271,"rel":272,"target":101},"https://www.chess.com/terms/stockfish-chess-engine",[98,99,100],"Stockfish",", and as we'll see later on, it's not always the perfect way to judge which player has the better or easier to play position.",[258,276,277],{},[90,278,279,280,283],{},"When computer evaluations go below zero, this means black has the better position. If they are positive, then ",[125,281,282],{},"white has the better position",". The unit of measurement for evaluations is called centipawns, as +/- 1 is roughly understood to be an advantage equal to starting the game with one more pawn than your opponent.",[90,285,286],{},[241,287,288],{},"Other important columns include:",[235,290,291,297,309,330],{},[238,292,293,296],{},[241,294,295],{},"Opening:"," the common name for the first couple of moves made in the game",[238,298,299,302,303,308],{},[241,300,301],{},"[White/Black]Rating:"," the ",[94,304,307],{"href":305,"rel":306,"target":101},"https://www.chess.com/terms/elo-rating-chess",[98,99,100],"ELO Rating"," of each of the players. It is a historically tried and true measure of their relative strength.",[238,310,311,314,315,318,319,322,323,326,327],{},[241,312,313],{},"TimeControl:"," in the form ",[241,316,317],{},"'N+K'",", where ",[241,320,321],{},"N"," is the number of minutes each player starts with, and ",[241,324,325],{},"K"," is their ",[125,328,329],{},"increment",[238,331,332,335,336,339,340,343],{},[241,333,334],{},"Event:"," a categorical grouping of the time control, into ",[125,337,338],{},"Ultrabullet, Bullet, Blitz, Rapid"," and ",[125,341,342],{},"Classical",".",[258,345,346],{},[90,347,348,349,351],{},"Increment is the amount of time that gets added to a player's game clock ",[241,350,247],{}," making a move. If a player's clock reaches 0, they lose instantly, even if a move they were just about to make would have added time to their clock.",[85,353,355],{"id":354},"exploratory-data-analysis","Exploratory Data Analysis",[90,357,358],{},"The first step in understanding the dataset is to calculate some descriptive statistics. Below are histograms of the most important features that describe the games, such as the rating of the players, the length of the game, and more. Originally the code block below contained 5 lines of brute-force 'code', where I wrote out the lists containing the rating ranges and labels manually after looking through the data, before realising that this approach kind of defeats the purpose of programming. So I wrote a function that will create the rating ranges before plotting, ensuring the graph will still look correct even after the data cleaning process. The result is a quite a couple of lines longer, and took more time to write, but is also a lot more reusable and taught me a lesson; a tradeoff I am more than willing to make.",[360,361],"code-snippet",{"file":362,":collapsible":363},"descriptive-stats","true",[365,366],"enlarge-image",{":background":363,"alt":367,"src":368},"Descriptive statistics before cleaning","/blog/chess-games/EDA_before.png",[90,370,371],{},"From this, we see there's some anomalies that can be removed to get a clearer picture. Rules infractions and abandoned games can go, as well as games where the result seems inconclusive ('*' in the result column). I considered removing draws as well since they only comprise of a small amount of games but their inclusion doesn't hurt our results so I decided to leave them in. I also removed bullet and ultra bullet games since they are so short that no meaningful fluctuation in time spent can be observed.",[90,373,374],{},"Increment has a lot of unusual values which are not commonly used. This is probably due to people creating custom game challenges with less used or obscure time controls.\nWe can remove the odd ones out to get a clearer picture of the ones that are commonly found in online chess games.",[90,376,377],{},"99% of games are a total of 75 moves or shorter, which indicates that we can remove the last 50 ply without losing much data, significantly speeding up the analysis, as well as reducing the skewness of those distributions. We cut off some games before their conclusion, but since we're analyzing on a per-move basis, this should pose too much of a problem .",[90,379,380],{},"Finally, to reduce the skew of the rating distribution and make the results more applicable to the general population, we will only look at games with a maximum rating of 2500 for either player, and a rating difference of no more than 200 between them. This is to remove any confounding factors such as the stress / complacency of playing against a higher or lower rated player, respectively.",[90,382,383,384],{},"It's possible that Blitz games prove to be too short as well, so we might revisit this at a later moment.\nBut for now, ",[125,385,386],{},"let's clean some data!",[85,388,390],{"id":389},"data-cleaning","Data cleaning",[360,392],{"file":393},"cleaning",[360,395],{"file":396},"cleaning-output",[90,398,399],{},"These relatively simple filters make the dataset more normalized and remove outliers on key features.\nNow we can plot the descriptive stats again to see if things improved:",[365,401],{":background":363,"alt":402,"src":403},"After EDA","/blog/chess-games/EDA_after.png",[90,405,406],{},"Those graphs already look a lot cleaner.\nRating distribution skewness went from 0.24 to 0.14, and game length skewness from 1.17 to 0.68. This makes the distribution measurably more symmetrical.\nRemoving the odd increments also decreases the influence that rare custom time controls have on the data.",[85,408,410],{"id":409},"extracting-time-and-evaluation-difference","Extracting Time and Evaluation Difference",[90,412,413],{},"To be able to answer our research question, we need to make a dataframe containing the time spent on each move, together with the evaluation difference between that move and the next one. An important factor to include is the increment the player gets, as it will be added to the clock after each move and thus isn't actually time spent on making the move itself.",[90,415,416],{},"First, we make filtered dataframes containing only the relevant columns. Then we combine those, before finally starting on the bulk of the data processing for this project. The final pipeline is the result of a lot of trial and error, pushing my Python skills to it's limit.",[90,418,419],{},"I've tried to explain my thought process as much as possible, but if you just want to go to the results, you can click the button below to collapse the code.",[360,421],{"file":422,":collapsible":363},"time-eval",[85,424,426],{"id":425},"results","Results",[90,428,429],{},"Now that we have our DataFrame on a per move basis, a second round of data examination and cleaning begins.\nFirst, let's look at the boxplots for evaluation change and time spent to check for any outliers. The scatterplot also helps show how the data is distributed.",[365,431],{":background":363,"alt":432,"src":433},"Move evaluation boxplot","/blog/chess-games/move_eval_boxplot.png",[365,435],{":background":363,"alt":436,"src":437},"Evaluation time spend scatterplot","/blog/chess-games/eval_time_scatter.png",[90,439,440],{},"There are some unexpected things happening here:",[90,442,443,450],{},[125,444,445,446,449],{},"Some moves seem to take ",[125,447,448],{},"negative time"," to make",", even after taking the increment into account.\nWhile it could potentially be an error in the code, this is likely due to a feature on lichess where your opponent can give you extra time on your clock,\nwhich will show up as negative time spent on that move.\nSome of the extremely large time spent values can also be due to this feature, as it allows a player to have much more time on their clock than the format should allow. With some exceptions, most of the large time spent comes from classical games, which can last for hours, so that makes sense.",[90,452,453,454,457,458,461,462,465],{},"This is also where we see a problem arise with the ",[241,455,456],{},"interpretability of computer evaluations",".\nDuring the processing, we ommitted any move where the current or previous move contained a forced mate evaluation, as those cannot be easily converted into a numerical value. Mate in one, two or three moves can be conceptualized as being relatively easy to find, but if the computer sees forced mate in 20 moves, and ",[125,459,460],{},"every other sequence of moves"," leads to a much worse position, the position will be ",[241,463,464],{},"much worse in practical terms",". A chess computer has no idea on how to differentiate those, as the evaluation of a position is based on the best move in that position.\nSimilarly, when a position is really good but the computer can't find a sequence leading to forced mate, it can produce some crazy high evaluations.",[90,467,468,471],{},[241,469,470],{},"Good to know:"," chess evaluation is supposed to be mapped back to the value of the pieces. So if the evaluation is +9, this should roughly mean white is up the equivalent of a full queen.",[258,473,474],{},[90,475,476,479],{},[241,477,478],{},"This leads to some questions about interpretability",":",[235,481,482,485],{},[238,483,484],{},"If the evaluation is already +20 for your opponent, and your move takes it to +50, did the position get worse in human terms?",[238,486,487],{},"If the inverse happens, and you manage to 'only' be down the equivalent of two queens instead of four, did your chances to win really improve?",[90,489,490,491,494,495,500],{},"In other words, ",[241,492,493],{},"computer evaluation cannot be seen as a continuous scale on which to evaluate a position",". Go past a threshold of, say, 10 and subsequent increases start to matter less and less, in practical human terms.\nComparison with the second best computer move would provide some insight into how critical finding a specific move is, but that isn't available in this dataset.\nFor more analysis about the relationship between computer evaluation and winning chances, check out ",[94,496,499],{"href":497,"rel":498,"target":101},"https://web.chessdigits.com/articles/when-should-you-resign",[98,99,100],"this article"," by the creator of the dataset.",[85,502,504],{"id":503},"whats-next","What's next?",[90,506,507,508,511],{},"Originally, and perhaps naively, I was planning to just do regression and correlation analyses on the extracted data.\nBut after delving into the results, it seems that it is very hard to obtain useful insights from computer evaluation change ",[241,509,510],{},"as an absolute numerical value",".\nIts value and relevancy fluctuates too drastically based on how close to 0 it is.",[90,513,514,515,518,519,343],{},"A more reasonable approach would be to turn evaluation changes into ",[241,516,517],{},"categories",":\nAnalyzing data like this removes the scale problem of evaluations, and focuses on the question at hand; if spending more time leads to fewer ",[125,520,521],{},"mistakes",[90,523,524],{},"To address this, we can categorize evaluation changes into three main types:",[235,526,527,533,539],{},[238,528,529,532],{},[241,530,531],{},"Blunder:"," A move that drastically worsens the evaluation, turning a balanced or winning position into a losing one.",[238,534,535,538],{},[241,536,537],{},"Mistake:"," A move that worsens the evaluation, but not as severely as a blunder, reducing a winning advantage to a smaller one.",[238,540,541,544],{},[241,542,543],{},"Inaccuracy:"," A move that slightly worsens the evaluation, but doesn't change the overall assessment of the position.",[90,546,547],{},"By grouping moves this way, we can analyze whether spending more time reduces the frequency of blunders and mistakes, rather than focusing on raw evaluation numbers.\nThis approach aligns better with how chess players and coaches assess move quality in practical terms.",[549,550],"br",{},[552,553],"hr",{},[549,555],{},[90,557,558],{},"For those of you that have read this post all the way through, thank you so much! Your attention and support means a lot to me.\nAs a parting gift, I made this nice graph of the top 20 most commonly played openings in the dataset. Check it out below!",[549,560],{},[365,562],{":background":363,"alt":563,"src":564},"Win percentage graph","/blog/chess-games/win_percentages_transparent.png",{"title":177,"searchDepth":178,"depth":178,"links":566},[567,568,569,570,571,572],{"id":196,"depth":181,"text":197},{"id":354,"depth":181,"text":355},{"id":389,"depth":181,"text":390},{"id":409,"depth":181,"text":410},{"id":425,"depth":181,"text":426},{"id":503,"depth":181,"text":504},"2025-11-14","Chess is as much about finding the best move as it is about managing your available time, but does going *'into the tank'* actually result in a better move? \nI analyze hundreds of thousands of moves across 200.000 games, and learn a thing or two about continuous variables in the process.\n","/blog/chess-games/cover.jpg",{},{"title":10,"description":574},"jptXXDPmmiofQSZ6SDEdchQpU5JKj2pn_Nq4IJpFdPI",{"data":580,"body":581},{},{"type":582,"children":583},"root",[584],{"type":585,"tag":90,"props":586,"children":587},"element",{},[588],{"type":589,"value":590},"text","Sometimes, when all out-of-the-box solutions fall short, you have to come up with your own.\nRead here how I solved a reporting problem using tools beyond what they were originally meant for.",{"data":592,"body":593},{},{"type":582,"children":594},[595],{"type":585,"tag":90,"props":596,"children":597},{},[598,600,605],{"type":589,"value":599},"Chess is as much about finding the best move as it is about managing your available time, but does going ",{"type":585,"tag":125,"props":601,"children":602},{},[603],{"type":589,"value":604},"'into the tank'",{"type":589,"value":606}," actually result in a better move?\nI analyze hundreds of thousands of moves across 200.000 games, and learn a thing or two about continuous variables in the process.",1792076567162]