{"id":3238,"date":"2026-05-12T12:10:21","date_gmt":"2026-05-12T10:10:21","guid":{"rendered":"https:\/\/geekosas.com\/?p=3238"},"modified":"2026-05-23T12:47:27","modified_gmt":"2026-05-23T10:47:27","slug":"back-to-office-post-covid19-2","status":"publish","type":"post","link":"https:\/\/geekosas.com\/index.php\/2026\/05\/12\/back-to-office-post-covid19-2\/","title":{"rendered":"Back to Office Post-Covid19"},"content":{"rendered":"<p>Before starting this article I want to thank Tristan Riquelme for giving me the idea, he is someone who always has excellent ideas about where to apply mathematical tools to real problems.<\/p>\n<p>Now starting with the article:<br \/>\nAlthough we find a definitive cure for COVID-19, it has already created cultural changes that will stay with us for a long time.<br \/>\nOne of the big changes is that it was demonstrated that remote work is a possibility and in many cases more efficient than in-person work, but that doesn&#8217;t mean there are no benefits to in-person work. That is why many companies have opted for a hybrid system with in-person shifts, which adjust to existing capacity limits and the needs of the company.<br \/>\nIn a large company, deciding which day each collaborator goes to work is a fairly complex task that covers many dimensions, for example:<\/p>\n<ul>\n<li>that the right people meet each other<\/li>\n<li>that capacity limits are met<\/li>\n<li>each collaborator&#8217;s preferences<\/li>\n<li>number of times per week each one must go<\/li>\n<\/ul>\n<p>The computer is not smarter than people, but it does have more eyes, that is why these cases with many variables are better left to the computer. The tool we will use will be mathematical programming, where we will use it to decide which day each collaborator goes to work, according to their preferences and the needs of the company.<\/p>\n<p>In this case, we are going to define that the people who must meet in the office are those from the same team, we will make each person belong to one day per week, everyone on a team will have to go to the office and thus carry out team activities.<\/p>\n<h3>Model Formulation<\/h3>\n<h4>Load Datasets<\/h4>\n<p>To consider each collaborator&#8217;s preferences, we will ask each one to assign a score to each day as follows:<br \/>\n<img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2021\/10\/image-1635109694128.png?ssl=1\" alt=\"file\" \/><\/p>\n<p>We will start by loading the data into python and preparing the data:<\/p>\n<pre><code class=\"language-python\">>&gt;&gt; import pulp\n>&gt;&gt; import itertools\n>&gt;&gt; import pandas as pd\n>&gt;&gt; pref = pd.read_excel(&quot;data_con_pref.xlsx&quot;,&quot;pref&quot;)\n>&gt;&gt; pref = pref.fillna(0)\n>&gt;&gt; print(pref)\n       lun  mar  mie  jue  vie\ncolab                         \nc1       1    4   -5    5   -5\nc2      -3    5   -3    5   -4\nc3       3    3   -5    4   -5\nc4      -2    1    8    1   -8\nc5      -4   -6    1    7    2\nc6       3    3    3    1  -10\nc7       2    2    2    2    2\nc8      -8    3    3   -2    4\nc9      -2   -8    4    2    4\nc10      4    3    3   -5   -5\nc11      0    5    2    3  -10\nc12      1   -3   -7    8    1\nc13      5    5   -3   -3   -4\nc14     -2    5    5   -2   -6\nc15      2   -3   -4   -3    8<\/code><\/pre>\n<p>The preferences will be used to calculate the benefit of following them for the model, but obviously forcing a person to go to work on a day that is not their preference is quite annoying, so after scaling the scores, we will multiply the negative values by 2.<\/p>\n<pre><code class=\"language-python\">>&gt;&gt; #scaled to 1 and double weighting of negatives\n>&gt;&gt;\n>&gt;&gt; for c in pref.index:\n...     pref.loc[c,:] = pref.loc[c,:]\/pref.loc[c,:].abs().sum()\n...     pref.loc[c,:] = [x if x &gt; 0 else 2*x for x in pref.loc[c,:]]\n...\n>&gt;&gt; print(pref)\n        lun   mar   mie   jue   vie\ncolab                              \nc1     0.05  0.20 -0.50  0.25 -0.50\nc2    -0.30  0.25 -0.30  0.25 -0.40\nc3     0.15  0.15 -0.50  0.20 -0.50\nc4    -0.20  0.05  0.40  0.05 -0.80\nc5    -0.40 -0.60  0.05  0.35  0.10\nc6     0.15  0.15  0.15  0.05 -1.00\nc7     0.20  0.20  0.20  0.20  0.20\nc8    -0.80  0.15  0.15 -0.20  0.20\nc9    -0.20 -0.80  0.20  0.10  0.20\nc10    0.20  0.15  0.15 -0.50 -0.50\nc11    0.00  0.25  0.10  0.15 -1.00\nc12    0.05 -0.30 -0.70  0.40  0.05\nc13    0.25  0.25 -0.30 -0.30 -0.40\nc14   -0.20  0.25  0.25 -0.20 -0.60\nc15    0.10 -0.30 -0.40 -0.30  0.40<\/code><\/pre>\n<p>in addition to a table with the membership of each team:<br \/>\n<img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2021\/10\/image-1635109747576.png?ssl=1\" alt=\"file\" \/><\/p>\n<pre><code class=\"language-python\">>&gt;&gt; team = pd.read_excel(&quot;data_con_pref.xlsx&quot;,&quot;team&quot;)\n>&gt;&gt; team = team.set_index(&#039;colab&#039;)\n>&gt;&gt; print(team)\n      team\ncolab     \nc1      t1\nc2      t1\nc3      t1\nc4      t1\nc5      t2\nc6      t2\nc7      t2\nc8      t2\nc9      t2\nc10     t3\nc11     t3\nc12     t3\nc13     t3\nc14     t3\nc15     t3<\/code><\/pre>\n<h4>Dimensions<\/h4>\n<p>They are like the coordinates of the data<\/p>\n<ul>\n<li><strong>colab<\/strong>: collaborators<\/li>\n<li><strong>dias<\/strong>: day of the week<\/li>\n<li><strong>team<\/strong>: team<\/li>\n<\/ul>\n<p>we extract them from the data itself:<\/p>\n<pre><code class=\"language-python\"># indices\ndias = pref.columns\ncolab = pref.index\nteams = set(team[&quot;team&quot;])<\/code><\/pre>\n<h4>Decision Variables<\/h4>\n<p>Which day each collaborator goes: 1 if they go on a day, 0 if not:<\/p>\n<pre><code class=\"language-python\">CD = {e:{d:pulp.LpVariable(cat =&#039;Binary&#039;, name =&#039;{} el {}&#039;.format(e, d)) for d in dias} for e in emp}<\/code><\/pre>\n<p>Which day each team will meet<\/p>\n<pre><code class=\"language-python\">TD = {t:{d:pulp.LpVariable(cat =&#039;Binary&#039;, name =&#039;{} el {}&#039;.format(t, d)) for d in dias} for t in teams}<\/code><\/pre>\n<h4>Objective Function<\/h4>\n<p>The objective function is what allows us to compare different shift combinations with a number.<\/p>\n<p>The objective function is debatable, there could be many, for example: maximize the worst-off collaborator with respect to their preferences or maximize the overall benefit. In this case we will build the second case, where there is a benefit for going on preferred days and for not going on days without preference or with a negative score:<\/p>\n<p><code class=\"katex-inline\">obj = \\sum_{c,d}{CD_{c,d} * pref_{c,d} - (1-CD_{c,d}) * pref_{c,d} }<\/code><\/p>\n<pre><code class=\"language-python\">prob = pulp.LpProblem(&quot;DIAS&quot;, pulp.LpMaximize)\nprob += pulp.lpSum([ CD[e][d] * pref.loc[e, d] - (1 - CD[e][d]) * pref.loc[e, d] for e, d in itertools.product(emp, dias)])<\/code><\/pre>\n<h4>Constraints<\/h4>\n<p>Finally we will define the interaction of the variables as constraints that reflect the conditions that make a combination of shifts valid:<\/p>\n<h5>Everyone goes 2 times a week<\/h5>\n<p><code class=\"katex-inline\">\\forall e \\in colab \\sum_{d \\in dias} CD_{c,d} = 2<\/code><\/p>\n<pre><code class=\"language-python\">for e in emp:\n    prob += pulp.lpSum([CD[e][d] for d in dias]) == 2<\/code><\/pre>\n<h5>Each team has its day<\/h5>\n<p><code class=\"katex-inline\">\\forall t \\in team \\sum_{d \\in dias} TD_{t,d} = 1<\/code><\/p>\n<pre><code class=\"language-python\">for t in teams:\n    prob += pulp.lpSum([TD[t][d] for d in dias]) == 1<\/code><\/pre>\n<h5>Everyone goes on their team&#8217;s day<\/h5>\n<pre><code class=\"language-python\">for e, t, d in itertools.product(colab,teams,dias):\n    if team.loc[e,&#039;team&#039;] == t:\n        prob += CD[e][d] &gt;= TD[t][d]<\/code><\/pre>\n<h5>Maximum capacity of 10 people per day in the office<\/h5>\n<p><code class=\"katex-inline\">\\forall d \\in dias \\sum_{e \\in colab} CD_{e,d} <= 10<\/code><\/p>\n<pre><code class=\"language-python\">for d in dias:\n    prob += pulp.lpSum([CD[e][d] for e in emp]) &lt;= 10<\/code><\/pre>\n<h4>We solve the problem and look at the results:<\/h4>\n<pre><code class=\"language-python\">solver = pulp.getSolver(&#039;PULP_CBC_CMD&#039;)\nprob.solve(solver)<\/code><\/pre>\n<h4>We look at the solution<\/h4>\n<p>We see that in this case only 2 collaborators will have to go on days that are not their preference<\/p>\n<pre><code class=\"language-python\">>&gt;&gt; for e,d in itertools.product(colab,dias):\n...     if(pulp.value(CD[e][d]) &gt; 0.9):\n...         print(&#039;{} goes on {} and is {}&#039;.format(e,d,&#039;ok&#039; if pref.loc[e,d] &gt; 0 else &#039;not ok&#039;))\n...\nc1 goes on tue and is ok\nc1 goes on thu and is ok\nc2 goes on tue and is ok\nc2 goes on thu and is ok\nc3 goes on tue and is ok\nc3 goes on thu and is ok\nc4 goes on wed and is ok\nc4 goes on thu and is ok\nc5 goes on wed and is ok\nc5 goes on thu and is ok\nc6 goes on mon and is ok\nc6 goes on wed and is ok\nc7 goes on tue and is ok\nc7 goes on wed and is ok\nc8 goes on wed and is ok\nc8 goes on fri and is ok\nc9 goes on wed and is ok\nc9 goes on fri and is ok\nc10 goes on mon and is ok\nc10 goes on wed and is ok\nc11 goes on mon and is not ok\nc11 goes on tue and is ok\nc12 goes on mon and is ok\nc12 goes on thu and is ok\nc13 goes on mon and is ok\nc13 goes on tue and is ok\nc14 goes on mon and is not ok\nc14 goes on wed and is ok\nc15 goes on mon and is ok\nc15 goes on fri and is ok<\/code><\/pre>\n<p>obtaining a score of 17.4<\/p>\n<pre><code class=\"language-python\">>&gt;&gt;print(&quot;Objective Value&quot;)\n>&gt;&gt;print(pulp.value(prob.objective))\n17.400000000000006<\/code><\/pre>\n<p>if you want to see the code you can download it from my github: <a href=\"https:\/\/github.com\/danielfm123\/turnos_back_to_office\">https:\/\/github.com\/danielfm123\/turnos_back_to_office<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"mh-excerpt\"><p>Before starting this article I want to thank Tristan Riquelme for giving me the idea, he is someone who always has excellent ideas about where <a class=\"mh-excerpt-more\" href=\"https:\/\/geekosas.com\/index.php\/2026\/05\/12\/back-to-office-post-covid19-2\/\" title=\"Back to Office Post-Covid19\">[&#8230;]<\/a><\/p>\n<\/div>","protected":false},"author":1,"featured_media":3031,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[1],"tags":[],"class_list":["post-3238","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-sin-categoria"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/geekosas.com\/wp-content\/uploads\/2021\/09\/dressed_0617-780x710-1.jpg?fit=780%2C710&ssl=1","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p8vjqF-Qe","jetpack-related-posts":[{"id":3252,"url":"https:\/\/geekosas.com\/index.php\/2015\/04\/23\/wireless-music-receiver\/","url_meta":{"origin":3238,"position":0},"title":"Wireless Music Receiver","author":"Daniel Fischer","date":"2015-04-23","format":false,"excerpt":"The purpose of this blog has always been to teach about my various geek inventions, gadgets I buy, tricks to get the most out of some technology, or simply to teach you about something few people know about, such as: how to choose a bicycle or how to read headphone\u2026","rel":"","context":"In &quot;Sin categor\u00eda&quot;","block_context":{"text":"Sin categor\u00eda","link":"https:\/\/geekosas.com\/index.php\/category\/sin-categoria\/"},"img":{"alt_text":"icon","src":"https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2015\/01\/descarga-300x298.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":3314,"url":"https:\/\/geekosas.com\/index.php\/2017\/05\/23\/estimation-of-vote-distribution-in-the-second-round-of-the-chilean-presidential-elections-2017\/","url_meta":{"origin":3238,"position":1},"title":"Estimation of vote distribution in the second round of the Chilean presidential elections 2017","author":"Daniel Fischer","date":"2017-05-23","format":false,"excerpt":"This article was made in a hurry and with little data; the definitive one is at Who voted for each candidate? After the first round of the Chilean presidential elections, it is traditional for each candidate to begin \"auctioning off\" the votes of their followers to the candidate they feel\u2026","rel":"","context":"In &quot;Sin categor\u00eda&quot;","block_context":{"text":"Sin categor\u00eda","link":"https:\/\/geekosas.com\/index.php\/category\/sin-categoria\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/geekosas.com\/wp-content\/uploads\/2017\/12\/segunda-vuelta.jpg?fit=710%2C399&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/geekosas.com\/wp-content\/uploads\/2017\/12\/segunda-vuelta.jpg?fit=710%2C399&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/geekosas.com\/wp-content\/uploads\/2017\/12\/segunda-vuelta.jpg?fit=710%2C399&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/geekosas.com\/wp-content\/uploads\/2017\/12\/segunda-vuelta.jpg?fit=710%2C399&ssl=1&resize=700%2C400 2x"},"classes":[]},{"id":3349,"url":"https:\/\/geekosas.com\/index.php\/2019\/05\/23\/uncovering-averages-part-2\/","url_meta":{"origin":3238,"position":2},"title":"Uncovering averages Part 2","author":"Daniel Fischer","date":"2019-05-23","format":false,"excerpt":"A few days ago I wrote the article Uncovering Averages which basically did was to open an average value into factors using trees. Please read the article before continuing. In that example analysis I created the dataset and therefore knew exactly where the change was, which was in the cause\u2026","rel":"","context":"In &quot;Sin categor\u00eda&quot;","block_context":{"text":"Sin categor\u00eda","link":"https:\/\/geekosas.com\/index.php\/category\/sin-categoria\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/geekosas.com\/wp-content\/uploads\/2019\/08\/averages.jpeg?fit=500%2C345&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":3266,"url":"https:\/\/geekosas.com\/index.php\/2016\/05\/23\/about-linux\/","url_meta":{"origin":3238,"position":3},"title":"About Linux","author":"Daniel Fischer","date":"2016-05-23","format":false,"excerpt":"If one morning you wake up with the inspiration to discover something new and you click the Random Article button on Wikipedia, with a probability of 1\/1,233,000 you will land on the article about Linux. If you want to wait for that event, you can keep trying; if not, you\u2026","rel":"","context":"In &quot;Sin categor\u00eda&quot;","block_context":{"text":"Sin categor\u00eda","link":"https:\/\/geekosas.com\/index.php\/category\/sin-categoria\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/geekosas.com\/wp-content\/uploads\/2016\/04\/linux.png?fit=387%2C442&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":3304,"url":"https:\/\/geekosas.com\/index.php\/2017\/05\/23\/r-vs-ms-open-r-2\/","url_meta":{"origin":3238,"position":4},"title":"R vs MS open R","author":"Daniel Fischer","date":"2017-05-23","format":false,"excerpt":"Microsoft has realized the power of R, so it has integrated it into its systems, including Power BI and SQL Server 2017. Microsoft released a free version of R with some improvements, including native SQL Server connection, package versioning in MRAN (Microsoft Cran), and optimizations in the linear algebra package.\u2026","rel":"","context":"In &quot;Sin categor\u00eda&quot;","block_context":{"text":"Sin categor\u00eda","link":"https:\/\/geekosas.com\/index.php\/category\/sin-categoria\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2017\/10\/lm-1024x932.png?resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2017\/10\/lm-1024x932.png?resize=350%2C200 1x, https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2017\/10\/lm-1024x932.png?resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2017\/10\/lm-1024x932.png?resize=700%2C400 2x"},"classes":[]},{"id":3291,"url":"https:\/\/geekosas.com\/index.php\/2017\/05\/23\/movies-2016\/","url_meta":{"origin":3238,"position":5},"title":"Movies 2016","author":"Daniel Fischer","date":"2017-05-23","format":false,"excerpt":"Movies make us laugh, cry, and some... sleep, so I decided to do a small analysis on 2016 movies. As with Video Games and Data Science, we did web scraping from www.metacritic.com to generate a database, in which, for each movie we obtained the following information: Country of Origin Genres\u2026","rel":"","context":"In &quot;Sin categor\u00eda&quot;","block_context":{"text":"Sin categor\u00eda","link":"https:\/\/geekosas.com\/index.php\/category\/sin-categoria\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2017\/03\/histogramas-300x120.png?resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2017\/03\/histogramas-300x120.png?resize=350%2C200 1x, https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2017\/03\/histogramas-300x120.png?resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.geekosas.com\/wp-content\/uploads\/2017\/03\/histogramas-300x120.png?resize=700%2C400 2x"},"classes":[]}],"_links":{"self":[{"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/posts\/3238","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/comments?post=3238"}],"version-history":[{"count":1,"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/posts\/3238\/revisions"}],"predecessor-version":[{"id":3239,"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/posts\/3238\/revisions\/3239"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/media\/3031"}],"wp:attachment":[{"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/media?parent=3238"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/categories?post=3238"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/geekosas.com\/index.php\/wp-json\/wp\/v2\/tags?post=3238"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}