<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.0.1">Jekyll</generator><link href="https://www.xlwings.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.xlwings.org/" rel="alternate" type="text/html" /><updated>2026-08-16T07:41:49+00:00</updated><id>https://www.xlwings.org/feed.xml</id><title type="html">Python in Excel alternative: Open. Self-hosted. No limits.</title><subtitle>Write add-ins, custom functions (UDFs), and macros with Python in Excel. Run locally or on your infrastructure with all the packages you need.</subtitle><entry><title type="html">My thoughts on Python in Excel</title><link href="https://www.xlwings.org/blog/my-thoughts-on-python-in-excel" rel="alternate" type="text/html" title="My thoughts on Python in Excel" /><published>2024-06-11T00:00:00+00:00</published><updated>2024-06-11T00:00:00+00:00</updated><id>https://www.xlwings.org/blog/my-thoughts-on-python-in-excel</id><content type="html" xml:base="https://www.xlwings.org/blog/my-thoughts-on-python-in-excel">&lt;p&gt;&lt;img src=&quot;/assets/images/blog/python-in-excel/python-in-excel.png&quot; alt=&quot;Python in Excel&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;alert alert-warning&quot; role=&quot;alert&quot;&gt;
  UPDATE: &lt;a href=&quot;https://lite.xlwings.org&quot; target=&quot;_blank&quot;&gt;xlwings Lite&lt;/a&gt;, my answer to Python in Excel, is now available in the official  &lt;a href=&quot;https://appsource.microsoft.com/en-us/product/office/WA200008175&quot; target=&quot;_blank&quot;&gt;Excel add-in store&lt;/a&gt; for free.
&lt;/div&gt;

&lt;p&gt;On August 22, 2023, Microsoft revealed the preview of “Python in Excel”. As creator of &lt;a href=&quot;https://www.xlwings.org&quot;&gt;xlwings&lt;/a&gt; and author of the O’Reilly book &lt;a href=&quot;https://www.amazon.com/Python-Excel-Environment-Automation-Analysis/dp/1492081000&quot;&gt;Python for Excel&lt;/a&gt;, I was obviously curious to give it a try. Since you shouldn’t judge a book by its cover, I decided to take a deep dive and record a LinkedIn Learning course on &lt;a href=&quot;https://www.linkedin.com/learning/python-in-excel-working-with-pandas-dataframes&quot;&gt;Python in Excel: Working with pandas DataFrames&lt;/a&gt;. Along the way I discovered many bugs and other issues and &lt;a href=&quot;https://github.com/microsoft/python-in-excel/issues?q=is%3Aissue+author%3Afzumstein&quot;&gt;reported them on GitHub&lt;/a&gt;. All of them are either resolved or at least acknowledged/commented by now, which is fantastic, and I only wish it was as easy to report a bug in the Office.js and VBA world.&lt;/p&gt;

&lt;p&gt;So what do I think about Python in Excel? Here’s the TL;DR:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We wanted an alternative to VBA, but got an alternative to the Excel formula language&lt;/li&gt;
  &lt;li&gt;Integrating the Jupyter notebook cells inside the Excel grid was a mistake&lt;/li&gt;
  &lt;li&gt;Python in Excel isn’t suitable for Python beginners nor for interactive data analysis&lt;/li&gt;
  &lt;li&gt;Right now, there are too many restrictions (can’t use your own packages and can’t connect to web APIs)&lt;/li&gt;
  &lt;li&gt;Here are the current use cases I see for Python in Excel:
    &lt;ul&gt;
      &lt;li&gt;Computationally intensive things like Monte Carlo simulations&lt;/li&gt;
      &lt;li&gt;AI stuff via the included packages (scikit-learn, nltk, statsmodels, imbalanced-learn, gensim)&lt;/li&gt;
      &lt;li&gt;Advanced visualizations via Matplotlib/Seaborn&lt;/li&gt;
      &lt;li&gt;Time-series analysis (this is one of Excel’s blind spots)&lt;/li&gt;
      &lt;li&gt;Not sure about data cleaning/data analysis: since you almost certainly need Power Query, it may actually be simpler and faster to just stick to Power Query  (instead of using Power Query and Python in Excel together)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we dive into the details, I want to clarify that this is just my personal opinion and is not meant to be a rant or critique. I’ve been in contact with the Excel team a few times along the way, and they are all super friendly and I value their interest in my opinion. Unlike me, they have to work with almost 40 years of Excel history and a ton of corporate overhead. Given these restrictions, they are doing an excellent job!&lt;/p&gt;

&lt;p&gt;(Also check out the related discussions on &lt;a href=&quot;https://news.ycombinator.com/item?id=40656125&quot;&gt;HackerNews&lt;/a&gt; and &lt;a href=&quot;https://www.reddit.com/r/Python/comments/1de2hkw/my_thoughts_on_python_in_excel/&quot;&gt;Reddit&lt;/a&gt;.)&lt;/p&gt;

&lt;h2 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#make-something-people-want&quot; id=&quot;markdown-toc-make-something-people-want&quot;&gt;Make something people want&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-mistake-placing-the-jupyter-notebooks-cells-inside-the-grid&quot; id=&quot;markdown-toc-the-mistake-placing-the-jupyter-notebooks-cells-inside-the-grid&quot;&gt;The mistake: placing the Jupyter notebooks cells inside the grid&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#python-in-excel-the-cloud&quot; id=&quot;markdown-toc-python-in-excel-the-cloud&quot;&gt;Python in &lt;del&gt;Excel&lt;/del&gt; the Cloud&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#a-glue-language-without-glue&quot; id=&quot;markdown-toc-a-glue-language-without-glue&quot;&gt;A glue language without glue&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#matplotlib-is-great-but-a-little-interactivity-wouldnt-hurt&quot; id=&quot;markdown-toc-matplotlib-is-great-but-a-little-interactivity-wouldnt-hurt&quot;&gt;Matplotlib is great, but a little interactivity wouldn’t hurt&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#no-access-to-the-excel-object-model&quot; id=&quot;markdown-toc-no-access-to-the-excel-object-model&quot;&gt;No access to the Excel object model&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#dataframes-are-great-but-so-are-numpy-arrays-and-lists&quot; id=&quot;markdown-toc-dataframes-are-great-but-so-are-numpy-arrays-and-lists&quot;&gt;DataFrames are great, but so are NumPy arrays and lists&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#a-better-alternative-than-an-alternative-to-the-excel-formula-language&quot; id=&quot;markdown-toc-a-better-alternative-than-an-alternative-to-the-excel-formula-language&quot;&gt;A better alternative than “an alternative to the Excel formula language”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-are-my-plans-with-xlwings&quot; id=&quot;markdown-toc-what-are-my-plans-with-xlwings&quot;&gt;What are my plans with xlwings?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;make-something-people-want&quot;&gt;Make something people want&lt;/h2&gt;

&lt;p&gt;The Python in Excel story started in 2015 when user “Daniel” posted the following idea on Microsoft’s UserVoice page (which has been migrated to their new &lt;a href=&quot;https://feedbackportal.microsoft.com/feedback/idea/976c241d-74bd-ed11-83ff-000d3a1ab7d1&quot;&gt;feedback forum&lt;/a&gt; in the meantime):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/python-in-excel/uservoice.png&quot; alt=&quot;UserVoice&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The idea quickly became the most popular one clearly wanting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A scripting language&lt;/li&gt;
  &lt;li&gt;User-defined functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately, Python in Excel doesn’t support either of these two features. Instead of being an alternative to VBA, it’s designed to be an alternative to the Excel formula language. While I don’t think there’s anything wrong with Excel’s formula language, Python in Excel is really just a 2-dimensional Jupyter notebook inside the Excel grid. It uses a real Jupyter notebook kernel, by the way, that runs in the Azure cloud. This means that you can use notebook magics such as &lt;code class=&quot;highlighter-rouge&quot;&gt;%%timeit&lt;/code&gt;, which is a cool thing!&lt;/p&gt;

&lt;p&gt;John Lam, a Principal Architect on the project, told the  &lt;a href=&quot;https://realpython.com/podcasts/rpp/186/&quot;&gt;Real Python Podcast&lt;/a&gt; that this implementation was just thought to be “a crazy idea”, but eventually it was picked by those people who call the shots at Microsoft. In my opinion, it should have remained a crazy idea, as I will explain in the next section.&lt;/p&gt;

&lt;h2 id=&quot;the-mistake-placing-the-jupyter-notebooks-cells-inside-the-grid&quot;&gt;The mistake: placing the Jupyter notebooks cells inside the grid&lt;/h2&gt;

&lt;p&gt;Python in Excel could have been straightforward by integrating a Jupyter notebook as a task pane in Excel. A Python developer would feel right at home, with the benefit that they can directly read and write from the Excel cells. On the other hand, Excel users can start dabbling around with Python in a “standard” environment and don’t have to learn a new tool when they move on from Excel. So what’s wrong with placing the notebook cells inside the grid?&lt;/p&gt;

&lt;h3 id=&quot;it-looks-like-a-duck-but-it-aint-one&quot;&gt;It looks like a duck, but it ain’t one&lt;/h3&gt;

&lt;p&gt;Everyone knows how a spreadsheet works: you put in numbers and formulas in the cells, and everything just works because Excel figures out the dependencies of each cell and recalculates those that need to be updated when a specific input changes. It certainly doesn’t matter where on the spreadsheet these cells are.&lt;/p&gt;

&lt;p&gt;Well, Python in Excel breaks this core spreadsheet truth: despite behaving very similarly to user-defined functions (“UDFs”), PY cells are evaluated from left to right and top to bottom. This includes the sheets, so the first sheet gets calculated before the second sheet, etc.&lt;/p&gt;

&lt;h3 id=&quot;do-repeat-yourself&quot;&gt;Do repeat yourself&lt;/h3&gt;

&lt;p&gt;When Office Scripts was introduced, it was such a relief that the code (unlike with VBA) was stored outside of the Excel workbook on SharePoint/OneDrive. All of a sudden, you could use the same code with multiple workbooks, without having to do any copy/pasting. This also means you can fix your code in exactly one location when you discover a bug. Life is good. Actually, it would have been even better if the Office Scripts file would be a regular TypeScript file that could be tracked on GitHub, but that’s a different story.&lt;/p&gt;

&lt;p&gt;Now, Python in Excel stores the code in the Excel file itself. To the end user, the code lives in the cells, but technically, it’s stored under &lt;code class=&quot;highlighter-rouge&quot;&gt;/xl/pythonScripts.xml&lt;/code&gt; inside the Excel workbook (i.e, in the ZIP file). So back to copy/pasting, yay!&lt;/p&gt;

&lt;h3 id=&quot;magic-magic-everywhere&quot;&gt;Magic, magic everywhere!&lt;/h3&gt;

&lt;p&gt;Whenever you want to write out a pandas DataFrame (or any other Python object) to the Excel cells, you need to switch from Object to Values mode (the button on the left of the formula bar). There are 2 issues with that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;When the DataFrame uses the default integer index, it’s hidden when turning it into values. If that index is either text or non-default, it is shown. Magic!&lt;/p&gt;

    &lt;p&gt;Imagine that you have 100 PY cells, and in cell 3 you set the &lt;code class=&quot;highlighter-rouge&quot;&gt;id&lt;/code&gt; column as DataFrame index. This means that in cell 100, where you convert the Python object into values, you all of a sudden have one column more.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Switching from Object to Values mode changes the formula from &lt;code class=&quot;highlighter-rouge&quot;&gt;=PY(&quot;...&quot;, 1)&lt;/code&gt; to &lt;code class=&quot;highlighter-rouge&quot;&gt;=PY(&quot;...&quot;, 0)&lt;/code&gt;. This is, however, hidden from the user as the user only sees the Python code in the cell (the &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;...&quot;&lt;/code&gt; part of the formula). Magic!&lt;/p&gt;

    &lt;p&gt;I see two main issues with that:&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;It prevents you from referencing a cell with a Python object directly, as you’re always running the risk that someday, someone switches that cell to Values mode, which would break any formula that references this cell in object mode.&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;PY cells in object mode behave differently than any other object cell (“Excel (rich) data types”). Take the stocks data type: the cell is &lt;em&gt;always&lt;/em&gt; an object and you can show its price by doing &lt;code class=&quot;highlighter-rouge&quot;&gt;=A1.Price&lt;/code&gt; in a different cell. PY cells could behave the same: if you want the values, you would write &lt;code class=&quot;highlighter-rouge&quot;&gt;=A1.Values&lt;/code&gt;. A little consistency goes a long way.&lt;/p&gt;

        &lt;p&gt;&lt;img src=&quot;/assets/images/blog/python-in-excel/stocks-rich-datatype.png&quot; alt=&quot;Stocks rich data types&quot; /&gt;&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;non-interactive-data-analysis&quot;&gt;Non-interactive data analysis&lt;/h3&gt;

&lt;p&gt;I primarily use Jupyter notebooks for interactive data analysis or for trying things out in incremental steps. You type a few lines of code in a cell and hit &lt;code class=&quot;highlighter-rouge&quot;&gt;Shift+Enter&lt;/code&gt; to evaluate the cell, look at some intermediate result and continue with the next cell.&lt;/p&gt;

&lt;p&gt;With Python in Excel, I don’t see the output of the cell, unless I:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Change the cell type from Object to Values. That’s often not possible as this requires having enough space around the cell.&lt;/li&gt;
  &lt;li&gt;Click on the preview icon, which opens a pop-up window. That’s a lot of work (even with the &lt;code class=&quot;highlighter-rouge&quot;&gt;Ctrl+Shift+F5&lt;/code&gt; keyboard shortcut) and the preview window often isn’t all that helpful: it’s too small and the values are shown in the Excel cell representation instead of how a Jupyter notebook would show it. This is supposed to be fixed at some point, but right now, almost 1 year after the initial version, it still shows &lt;code class=&quot;highlighter-rouge&quot;&gt;#N/A&lt;/code&gt; in the preview so I don’t know if that is a &lt;code class=&quot;highlighter-rouge&quot;&gt;np.nan&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;pd.NA&lt;/code&gt;, or &lt;code class=&quot;highlighter-rouge&quot;&gt;pd.NaT&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Too much friction in the workflow means I am just not considering Python in Excel for interactive data analysis, unfortunately!&lt;/p&gt;

&lt;h3 id=&quot;diagnostics-paine&quot;&gt;Diagnostics pa(i)ne&lt;/h3&gt;

&lt;p&gt;I would call the diagnostics pane the biggest offender in Python in Excel. It starts with the unnecessarily technical name (why not simply call it &lt;code class=&quot;highlighter-rouge&quot;&gt;Output&lt;/code&gt; like in VS Code?).&lt;/p&gt;

&lt;p&gt;Anyhow, the diagnostics pane shows whatever you &lt;code class=&quot;highlighter-rouge&quot;&gt;print()&lt;/code&gt; in your cells as well as any error messages. So whatever a Jupyter notebook shows directly underneath the cell. But since Python in Excel pulls the output away from the cell, you have to manually associate the output back to the proper cell. Here are two examples:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;When looking at a DataFrame, typically the first thing you run is &lt;code class=&quot;highlighter-rouge&quot;&gt;df.info()&lt;/code&gt;. Now, since this prints the output, whenever Excel recalculates the Python cells, the Diagnostic pane shows up. That’s not just annoying, it’s also completely impossible to quickly relate the output on the diagnostics pane with the corresponding cell. It also keeps the output from the last run to make it even harder to find the lines that you’re after (a monospaced font would also help, by the way). So which output is my &lt;code class=&quot;highlighter-rouge&quot;&gt;df1&lt;/code&gt;, and which one is my &lt;code class=&quot;highlighter-rouge&quot;&gt;df2&lt;/code&gt;? I have no idea unless I look up the code that is sitting in cells &lt;code class=&quot;highlighter-rouge&quot;&gt;L2&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;K2&lt;/code&gt;:&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/assets/images/blog/python-in-excel/diagnostics-df-info.png&quot; alt=&quot;Diagnostics Pane Error&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The same happens with exceptions. Try to find the error in the following screenshot (the error happens in &lt;code class=&quot;highlighter-rouge&quot;&gt;K2&lt;/code&gt;, which has already disappeared at the bottom of the diagnostics pane)&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/assets/images/blog/python-in-excel/diagnostics-error.png&quot; alt=&quot;Diagnostics Pane Error&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a beginner Python coder, a crucial thing is to get easy feedback on your errors. That’s not the case here, which is why I don’t consider Python in Excel a good solution for Python beginners.&lt;/p&gt;

&lt;p&gt;As an idea, the preview pop-up could be a much better place to show the output without loosing the context. Also, coloring the cell or the PY icon in red when there’s an error could help further.&lt;/p&gt;

&lt;h2 id=&quot;python-in-excel-the-cloud&quot;&gt;Python in &lt;del&gt;Excel&lt;/del&gt; the Cloud&lt;/h2&gt;

&lt;p&gt;As you’ve probably heard, Python runs in the cloud on &lt;a href=&quot;https://azure.microsoft.com/en-us/products/container-instances&quot;&gt;Azure Container Instances&lt;/a&gt; and not inside Excel. There was a lot of backlash on social media about that (“privacy, privacy!”), but what these commenters are missing is that the majority of corporations already trust Microsoft with all their data by using OneDrive/SharePoint.&lt;/p&gt;

&lt;p&gt;In fact, I agree that the cloud is the best way to bring Python to Excel. The maintenance and installation of a local Python environment simply isn’t possible for the inexperienced and when teams are bigger than a handful of people. The only other option is Web-Assembly (WASM), but it looks like the Excel team left this as an exercise for me to implement (see the end of this post).&lt;/p&gt;

&lt;p&gt;What you need to keep in mind though is that Python in Excel will cost an extra subscription fee on top of Microsoft 365 (we don’t know how much yet) and there seems to be a quota: while I was recording my LinkedIn Learning course, I was greeted by this message:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/python-in-excel/python-in-excel-limit.png&quot; alt=&quot;Python in Excel Rate Limit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;While this is annoying enough when you’re supposed to record your videos, it’s pretty much unacceptable when you use your Excel spreadsheet for trading at a Hedge Fund (yes, that’s still a thing). And even if there would be no quota, the Azure cloud sometimes goes down and your Python cells won’t work—something to keep in mind.&lt;/p&gt;

&lt;p&gt;All this to say that I wouldn’t use Python in Excel for something critical as long as I can’t switch to a self-hosted or local server when the cloud is down.&lt;/p&gt;

&lt;h2 id=&quot;a-glue-language-without-glue&quot;&gt;A glue language without glue&lt;/h2&gt;

&lt;p&gt;Much of the reason why people love Python is that it’s a “glue language.” With Python you can connect what needs to be connected, as there’s a package for everything. So a typical workflow might look like this: you download a few files from a web API, get data from a database, push everything through a pandas or polars DataFrame and finally send out an email with the results.&lt;/p&gt;

&lt;p&gt;Well, such a basic workflow isn’t possible with Python in Excel. You can’t install any additional package and your code runs in a steel box, meaning that you can’t connect to the outside world via web API or database connection. The official solution is to use Power Query to acquire external data, as Python in Excel can then reference a Power Query. But really, one of the reasons I want Python and pandas in Excel is specifically to avoid Power Query (I don’t want to learn the M language), so I am not a big fan of this solution.&lt;/p&gt;

&lt;p&gt;Here’s what I don’t understand:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Python in Excel runs on Azure Container instances. And Azure Container Instances can use your own container with all the dependencies you wish for, so I can’t really understand why the Excel team said in one of their &lt;a href=&quot;https://www.reddit.com/r/excel/comments/16tohx5/we_developed_python_in_excel_one_of_microsoft/&quot;&gt;Reddit AMAs&lt;/a&gt; that the dependencies would have to be installed every time from scratch when somebody opens the workbook. That’s just BS as the container image is built once and then sits there with all the dependencies ready when somebody opens the workbook next time.&lt;/li&gt;
  &lt;li&gt;If I can access web APIs via Office Scripts, why not with Python in Excel? As far as I know, the Office admin can restrict what Office Scripts can access, so I’d expect something similar to arrive with Python in Excel one day.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In summary, missing packages and blocked internet access means that you can’t use Python in Excel for many of the use cases that had people scream for Python in Excel in the first place.&lt;/p&gt;

&lt;h2 id=&quot;matplotlib-is-great-but-a-little-interactivity-wouldnt-hurt&quot;&gt;Matplotlib is great, but a little interactivity wouldn’t hurt&lt;/h2&gt;

&lt;p&gt;Python in Excel offers Matplotlib and Seaborn (which is based on Matplotlib) to create plots where Excel charts may be too basic. That’s great and definitely needed, but the output is a boring static picture. xlwings has offered this for the last 10 years. What I am missing here is access to the modern interactive charts like Plotly, Bokeh, Altair, HoloViews, etc.&lt;/p&gt;

&lt;h2 id=&quot;no-access-to-the-excel-object-model&quot;&gt;No access to the Excel object model&lt;/h2&gt;

&lt;p&gt;People use Excel for reporting &lt;em&gt;all the time&lt;/em&gt;. And a super common use case is to consolidate one or more workbooks with data on multiple sheets. For example, there’s a sheet for each month or store location or department. With Python in Excel, you have to select the data on each sheet manually to get ahold of all the data:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;jan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Jan[#All]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;feb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Feb[#All]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Mar[#All]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Good luck if you have weekly data or 100 departments to consolidate. If Python in Excel would give you access to the Excel object model (like VBA or Office Scripts), it would look something like this (the sample shows xlwings syntax):&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;workbook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sheets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tables&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And talking about reporting: everyone wants their own color and cell border formatting, but without access to the Excel object model, that’s out of reach, too.&lt;/p&gt;

&lt;h2 id=&quot;dataframes-are-great-but-so-are-numpy-arrays-and-lists&quot;&gt;DataFrames are great, but so are NumPy arrays and lists&lt;/h2&gt;

&lt;p&gt;As soon as you select more than 1 cell, the &lt;code class=&quot;highlighter-rouge&quot;&gt;xl()&lt;/code&gt; function will deliver a pandas DataFrame. I don’t mind this being the default, but it still happens on a very regular basis that you need a list, a NumPy array (or a polars DataFrame!) instead of a pandas DataFrame. And really, this should be easy to get. In fact, you can write your own converter today that solves this problem, only I don’t want to copy/paste this to every workbook…! Here’s how you’d fix this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;myconverter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;convert&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;convert&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;convert&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;excel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;convert_to_dataframe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;convert&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;convert&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;convert&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; is not supported.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;excel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_xl_array_conversion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;myconverter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running this code in the Initialization pane (once it’ll be editable, for now place it in cell A1 of the 1st sheet), will allow you to use it the &lt;code class=&quot;highlighter-rouge&quot;&gt;xl()&lt;/code&gt; function as usual to get a pandas DataFrame:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A1:B2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But it will allow you to also get a NumPy Array:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A1:B2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;convert&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;or a list-of-lists:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mylist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A1:B2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;convert&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s just a few lines of code but this isn’t available right now in Python in Excel, which means code duplication and copy/pasting across workbooks.&lt;/p&gt;

&lt;h2 id=&quot;a-better-alternative-than-an-alternative-to-the-excel-formula-language&quot;&gt;A better alternative than “an alternative to the Excel formula language”&lt;/h2&gt;

&lt;p&gt;So I was asking myself: “Are there really no better alternatives than adding Python to improve the analytics capabilities of Excel’s formula language”? I think there is: by improving the native Excel formula language. And in recent years, that’s exactly what Microsoft has been doing. It all started with the dynamic arrays that first appeared in 2018 and that opened the door for powerful formulas like &lt;code class=&quot;highlighter-rouge&quot;&gt;=UNIQUE()&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;=SORT()&lt;/code&gt;. Most recently, Microsoft added the following new functions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;=PIVOT()&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;=GROUPBY()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wait a minute, that’s half of the reasons why I would create a pandas DataFrame in Excel, so instead of doing &lt;code class=&quot;highlighter-rouge&quot;&gt;df.pivot_table()&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;df.groupby()&lt;/code&gt;, I can now simply stay within native Excel and get proper Excel formulas without all the clunkiness of Python in Excel.&lt;/p&gt;

&lt;p&gt;What I find interesting though is the multiline editing experience of PY cells. Why not give the native Excel formula language an upgrade, so I don’t have to write &lt;code class=&quot;highlighter-rouge&quot;&gt;LET&lt;/code&gt; expressions such as the following one (that I still find very hard to read):&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;=LET(x, 1, y, 2, x + y)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instead, allow me to write it like so?&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let x = 1
let y = 2
x + y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Also, why not turn Excel tables into a native Excel DataFrame? Give them attributes instead of sticking to the functional approach and you’d be looking at something like this:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;=MyTable[#All].GROUPBY(...)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Lot’s of possibilities to integrate the pandas functionality in a way that feels more native to Excel!&lt;/p&gt;

&lt;h2 id=&quot;what-are-my-plans-with-xlwings&quot;&gt;What are my plans with xlwings?&lt;/h2&gt;

&lt;div class=&quot;alert alert-warning&quot; role=&quot;alert&quot;&gt;
  UPDATE: &lt;a href=&quot;https://lite.xlwings.org&quot; target=&quot;_blank&quot;&gt;xlwings Lite&lt;/a&gt;, my answer to Python in Excel, is now available in the official  &lt;a href=&quot;https://appsource.microsoft.com/en-us/product/office/WA200008175&quot; target=&quot;_blank&quot;&gt;Excel add-in store&lt;/a&gt; for free.
&lt;/div&gt;

&lt;p&gt;One of the most popular ways of using xlwings has always been to have a Jupyter notebook and Excel running side-by-side. The caveat is that Python is required to be installed locally. That’s an issue, because Python is hard to deploy and maintain with the exact packages you need. There’s also increasing pressure from IT security: they don’t like a full Python installation on an end-user’s machine.&lt;/p&gt;

&lt;p&gt;One solution to the problem is &lt;a href=&quot;https://docs.xlwings.org/en/latest/pro/server/index.html&quot;&gt;xlwings Server&lt;/a&gt;, which is self-hosted and runs everywhere where Python runs. However, the use case for xlwings Server is that of a developer, who writes macros and UDFs in Python on the backend. The end-user will access it in the form of a modern web-based Office.js add-in without having to know anything about Python. So it’s not thought for interactive use by the end-user.&lt;/p&gt;

&lt;p&gt;That’s why I have started to work on “xlwings Lite”, which will be based on Web-Assembly (WASM). Web-Assembly basically allows you to run Python natively in the browser, while Office.js add-ins run a browser inside Excel. So I “only” need to connect Python running on WASM with the Excel object model. &lt;a href=&quot;https://pyodide.org&quot;&gt;Pyodide&lt;/a&gt;, which is a Python distribution based on WASM, will help me achieve this.&lt;/p&gt;

&lt;p&gt;While it’s likely going to take me a few more months to implement this, what I can tell from an early proof-of-concept is that it will have these characteristics:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Supports true custom functions (aka user-defined functions “UDFs”)&lt;/li&gt;
  &lt;li&gt;Gives you access to the Excel object model&lt;/li&gt;
  &lt;li&gt;Supports Matplolib/Seaborn plots&lt;/li&gt;
  &lt;li&gt;Supports web API calls&lt;/li&gt;
  &lt;li&gt;It will allow to install your own packages&lt;/li&gt;
  &lt;li&gt;Privacy first: everything will run on the user’s machine&lt;/li&gt;
  &lt;li&gt;It’s fast&lt;/li&gt;
  &lt;li&gt;It’ll support Win, macOS, and Excel on the Web from day 1&lt;/li&gt;
  &lt;li&gt;You’ll be able to upload external files without Power Query&lt;/li&gt;
  &lt;li&gt;It’ll work on the permanent versions of Office like Office 2021, not just Microsoft 365&lt;/li&gt;
  &lt;li&gt;To get it, you will simply need to install an add-in from the Excel add-in store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically, I want to build Office Scripts, but with Python instead of JavaScript and running on the user’s machine instead of in the Microsoft cloud.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;There’s definitely a place for Python in Excel, but integrating a classic Jupyter notebook instead of placing the cells in the Excel sheet itself would have been much better, in my opinion.&lt;/p&gt;

&lt;p&gt;Don’t forget that Python in Excel is still in preview as of today, so we’re likely to get improvements and fewer restrictions in the future.&lt;/p&gt;</content><author><name>Felix Zumstein</name></author><category term="xlwings" /><category term="excel" /><category term="python" /><summary type="html"></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.xlwings.org/assets/images/blog/python-in-excel/python-in-excel.png" /><media:content medium="image" url="https://www.xlwings.org/assets/images/blog/python-in-excel/python-in-excel.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">A New Way to Automate Google Sheets with Python</title><link href="https://www.xlwings.org/blog/python-for-google-sheets" rel="alternate" type="text/html" title="A New Way to Automate Google Sheets with Python" /><published>2022-05-20T00:00:00+00:00</published><updated>2022-05-20T00:00:00+00:00</updated><id>https://www.xlwings.org/blog/python-for-google-sheets</id><content type="html" xml:base="https://www.xlwings.org/blog/python-for-google-sheets">&lt;p&gt;&lt;img src=&quot;/assets/images/blog/google-sheets/github.png&quot; alt=&quot;Google Sheets dashboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s been possible to automate Google Sheets via the &lt;a href=&quot;https://developers.google.com/sheets/api&quot;&gt;Google Sheets API&lt;/a&gt; for many years and there are multiple Python packages available that wrap this API: there’s &lt;a href=&quot;https://docs.gspread.org/&quot;&gt;gspread&lt;/a&gt;, &lt;a href=&quot;https://pygsheets.readthedocs.io/&quot;&gt;pygsheets&lt;/a&gt;, &lt;a href=&quot;https://github.com/asweigart/ezsheets&quot;&gt;ezsheets&lt;/a&gt;, and Google’s own &lt;a href=&quot;https://github.com/googleapis/google-api-python-client&quot;&gt;google-api-python-client&lt;/a&gt;. All these packages talk &lt;em&gt;from&lt;/em&gt; Python &lt;em&gt;to&lt;/em&gt; Google Sheets and require you to create a Google Cloud Platform project, download a credentials file, and potentially share your spreadsheet with the user from the credentials file. This can be a relatively painful process, especially if you’re new to Google’s Cloud Console. Nevertheless, the Sheets API approach is the right solution for many use cases: for example, dumping data into Google Sheets for reporting purposes or using Google Sheets as a database in your Python program.&lt;/p&gt;

&lt;p&gt;I, however, was missing the Google Apps Script experience in Python, i.e., I wanted to develop interactive spreadsheet apps directly in Google Sheets. So I went ahead and added Google Sheets support to &lt;a href=&quot;https://www.xlwings.org&quot;&gt;xlwings&lt;/a&gt;: instead of using the Sheets API to talk from Python to Google Sheets, xlwings goes the other way around and talks &lt;em&gt;from&lt;/em&gt; Google Sheets &lt;em&gt;to&lt;/em&gt; Python. The whole thing is really just a web app with a Google Sheets frontend (yes, it works with your favorite web framework!). Apart from sparing you the frontend work, this approach comes with a couple of nice features, such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Ease of use&lt;/strong&gt;: no need for a Google Cloud Platform project, a credentials file, or sharing with a specific user&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Authorization&lt;/strong&gt;: leverage the Google identity and restrict certain functionality to specific users or groups&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scheduler&lt;/strong&gt;: run your Python scripts automatically (e.g. every hour) via Google Sheets Triggers&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: no need to store credentials, proprietary source code, or other sensitive data in Google Sheets&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;API&lt;/strong&gt;: you’re not limited by the Sheets API, but can use everything that the Google Apps Script API offers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before walking you through these points in a little more detail, let me reiterate that xlwings is &lt;em&gt;not&lt;/em&gt; a replacement for the Sheets API approach (in fact, I am planning to wrap the Sheets API with xlwings, too)! Rather, it’s an alternative that allows you to create new types of spreadsheet applications that were previously only possible by writing JavaScript code. You could also look at it as a competitor to low-code tools for building internal apps (&lt;a href=&quot;https://retool.com/&quot;&gt;Retool&lt;/a&gt;, &lt;a href=&quot;https://www.internal.io/&quot;&gt;Internal&lt;/a&gt;, etc.). As a side benefit, xlwings code will also work with Microsoft Excel across Windows, macOS, and the web.&lt;/p&gt;

&lt;p&gt;Note that Google Sheets support is part of xlwings PRO: it’s free for &lt;a href=&quot;https://polyformproject.org/licenses/noncommercial/1.0.0/&quot;&gt;non-commercial use&lt;/a&gt; but requires a &lt;a href=&quot;https://www.xlwings.org/pricing&quot;&gt;paid plan&lt;/a&gt; for commercial use. And finally: this post stays high-level, so if you want more details, check out the sample GitHub repos that I will link to in this post, have a look at the corresponding &lt;a href=&quot;https://docs.xlwings.org/en/stable/remote_interpreter.html&quot;&gt;xlwings docs&lt;/a&gt;, or check my recent &lt;a href=&quot;https://youtu.be/uiugB0bgBG0&quot;&gt;webinar&lt;/a&gt;. By the way, the screenshot of the GitHub issues dashboard is taken from this &lt;a href=&quot;https://github.com/xlwings/xlwings-googlesheets-fastapi-github&quot;&gt;repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#how-does-the-traditional-sheets-api-work&quot; id=&quot;markdown-toc-how-does-the-traditional-sheets-api-work&quot;&gt;How does the traditional Sheets API work?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-xlwings-quickstart-for-google-sheets&quot; id=&quot;markdown-toc-the-xlwings-quickstart-for-google-sheets&quot;&gt;The xlwings quickstart for Google Sheets&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#authentication-and-authorization&quot; id=&quot;markdown-toc-authentication-and-authorization&quot;&gt;Authentication and authorization&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#security&quot; id=&quot;markdown-toc-security&quot;&gt;Security&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#run-your-code-via-buttons-custom-menus-and-triggers&quot; id=&quot;markdown-toc-run-your-code-via-buttons-custom-menus-and-triggers&quot;&gt;Run your code via buttons, custom menus, and triggers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#dataframes-and-plots&quot; id=&quot;markdown-toc-dataframes-and-plots&quot;&gt;DataFrames and plots&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#production-deployment&quot; id=&quot;markdown-toc-production-deployment&quot;&gt;Production deployment&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#roadmap&quot; id=&quot;markdown-toc-roadmap&quot;&gt;Roadmap&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;how-does-the-traditional-sheets-api-work&quot;&gt;How does the traditional Sheets API work?&lt;/h2&gt;

&lt;p&gt;Since all of the currently available Python packages such as &lt;a href=&quot;https://docs.gspread.org/&quot;&gt;gspread&lt;/a&gt; build on the Sheets API, their pre-requisites all look the same:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In the Google Cloud Console:
    &lt;ul&gt;
      &lt;li&gt;Create a Google Cloud Platform project&lt;/li&gt;
      &lt;li&gt;Enable the correct APIs&lt;/li&gt;
      &lt;li&gt;Create the proper credentials and download them as JSON file&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;You might need to share your spreadsheet with the user from the created credentials&lt;/li&gt;
  &lt;li&gt;Run your Python code from outside Google Sheets, e.g., by running it from the command line on your laptop or server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are lots of use cases where this approach works perfectly fine: running a cron job on a server to regularly push data into your sheet is a popular one. There are, however a few limitations with the Sheets API:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You can only automate what the Sheets API exposes: for example, inserting a picture on top of the cells isn’t supported&lt;/li&gt;
  &lt;li&gt;While cron jobs work great, you can’t have business users run your code ad-hoc without building some kind of app for them&lt;/li&gt;
  &lt;li&gt;Not everybody in your Google Workspace organization has the right to set up a Google Cloud Project&lt;/li&gt;
  &lt;li&gt;You need to make sure that the credentials file is properly secured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next section, we’ll see how xlwings works and how it ships around these issues.&lt;/p&gt;

&lt;h2 id=&quot;the-xlwings-quickstart-for-google-sheets&quot;&gt;The xlwings quickstart for Google Sheets&lt;/h2&gt;

&lt;p&gt;xlwings for Google Sheets is really just a web app that uses Google Sheets as the frontend, thereby saving you from having to deal with HTML, CSS, and JavaScript. It doesn’t require you to set up a Google Cloud Project, and boils down to just two steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Copy/paste the xlwings JavaScript module into the Apps Script editor&lt;/li&gt;
  &lt;li&gt;Run the Python web server with your xlwings code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The only caveat is that Google Sheets needs to be able to reach your web app:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;For development, you can use a cloud-based IDE such as &lt;a href=&quot;https://www.gitpod.io/&quot;&gt;GitPod&lt;/a&gt; or &lt;a href=&quot;https://github.com/features/codespaces&quot;&gt;GitHub Codespaces&lt;/a&gt; or—if you prefer to work on your local laptop—expose the port of your web server with a tool like &lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;For production, there isn’t anything special to consider as long as your server/cloud is reachable from Google Sheets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get up and running in literally 2 minutes, head over to the &lt;a href=&quot;https://github.com/xlwings/xlwings-remote-fastapi&quot;&gt;quickstart project on GitHub&lt;/a&gt; and hit the Gitpod button in the README. Then, in your Google Sheet, go to &lt;code class=&quot;highlighter-rouge&quot;&gt;Extensions&lt;/code&gt; &amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;Apps Script&lt;/code&gt; and paste the code you’ll find under &lt;code class=&quot;highlighter-rouge&quot;&gt;js/xlwings_google.js&lt;/code&gt;. That’s it!&lt;/p&gt;

&lt;p&gt;Hit the &lt;code class=&quot;highlighter-rouge&quot;&gt;Save&lt;/code&gt; button in the Apps Script editor before clicking on the &lt;code class=&quot;highlighter-rouge&quot;&gt;Run&lt;/code&gt; button to call the &lt;code class=&quot;highlighter-rouge&quot;&gt;hello&lt;/code&gt; function: this will write &lt;code class=&quot;highlighter-rouge&quot;&gt;Hello xlwings!&lt;/code&gt; into cell A1 of your spreadsheet, and clicking it again will change it to &lt;code class=&quot;highlighter-rouge&quot;&gt;Bye xlwings!&lt;/code&gt;. Note that if you do this the very first time, Google will ask you to authorize the script.&lt;/p&gt;

&lt;p&gt;How does this work? The &lt;code class=&quot;highlighter-rouge&quot;&gt;hello&lt;/code&gt; function looks like this (part of the JavaScript code that we pasted into the Apps Script editor):&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;runPython&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://your-url/hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;apiKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;your-api-key&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;runPython&lt;/code&gt; call requires the URL of your Python backend for that specific function and an API key. To see the Python code that we’re calling, open &lt;code class=&quot;highlighter-rouge&quot;&gt;app/main.py&lt;/code&gt; where you’ll see the corresponding endpoint:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Instantiate a Book object with the deserialized request body
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Use xlwings as usual
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sheets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello xlwings!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Bye xlwings!&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello xlwings!&quot;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Pass the following back as the response
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: I am using FastAPI in all of the sample repos, but you could use any other web framework like Django or Flask.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’ve used xlwings with Excel before, you’ll feel right at home, as you can use the same syntax. You can see the full code of a minimal quickstart sample either by looking at the &lt;a href=&quot;https://github.com/xlwings/xlwings-remote-fastapi&quot;&gt;repo on GitHub&lt;/a&gt; or by running the following command locally (requires xlwings to be installed): &lt;code class=&quot;highlighter-rouge&quot;&gt;xlwings quickstart &amp;lt;projectname&amp;gt; --fastapi&lt;/code&gt;. Note that you can also use the xlwings CLI to copy the Google Apps Script module: &lt;code class=&quot;highlighter-rouge&quot;&gt;xlwings copy gs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If this was a bit too fast, watch this video where I’ll walk you through every step:&lt;/p&gt;
&lt;lite-youtube videoid=&quot;1kN75agQwsk&quot; playlabel=&quot;Automate Google Sheets with Python &amp;amp; xlwings&quot; class=&quot;mb-4&quot;&gt;&lt;/lite-youtube&gt;

&lt;p&gt;Now that we’ve played around with a quickstart project, let’s have a look into a few of the features that xlwings offers in connection with Google Sheets.&lt;/p&gt;

&lt;h2 id=&quot;authentication-and-authorization&quot;&gt;Authentication and authorization&lt;/h2&gt;

&lt;p&gt;Authentication is one of the most critical parts of every app and is usually hard to get right. Google Sheets makes this part embarrassingly simple: replace the API key from the quickstart sample with &lt;code class=&quot;highlighter-rouge&quot;&gt;ScriptApp.getOAuthToken()&lt;/code&gt;, which allows the backend to verify the current user. This lets you:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Remove the sensitive API key from the Apps Script module&lt;/li&gt;
  &lt;li&gt;Authenticate everybody from your Workspace domain automatically&lt;/li&gt;
  &lt;li&gt;Authorize users for certain actions: for example, you can restrict write access to your database to a few users, while giving everybody read access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get a better idea, have a look at the following sample repo:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/xlwings/xlwings-googlesheets-fastapi-auth&quot;&gt;https://github.com/xlwings/xlwings-googlesheets-fastapi-auth&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It shows you:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;How to set up authentication&lt;/li&gt;
  &lt;li&gt;How to get the user object&lt;/li&gt;
  &lt;li&gt;How to perform authorization (using FastAPI’s scope-based approach)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How exactly you’re authorizing your users is up to you: you could use an identity service like Active Directory or Okta or define groups as environment variables. The sample repo shows you how to verify group membership directly against your Google Workspace—this, however, requires you to set up a Google Cloud Project, see the repo’s README for all the details.&lt;/p&gt;

&lt;p&gt;Related to authentication/authorization is security in general, which we’ll look at next.&lt;/p&gt;

&lt;h2 id=&quot;security&quot;&gt;Security&lt;/h2&gt;

&lt;p&gt;Sharing a Google Sheet is as easy as typing in an email address, but that also means that you can share it accidentally with the wrong person. To minimize the damage, xlwings makes it easy to keep sensitive data out of your spreadsheet:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Credentials for databases, APIs, etc.&lt;/li&gt;
  &lt;li&gt;Other sensitive data like client data&lt;/li&gt;
  &lt;li&gt;Business logic (your Python source code)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Apps Script module will only contain non-sensitive information, as long as you’re using &lt;code class=&quot;highlighter-rouge&quot;&gt;ScriptApp.getOAuthToken()&lt;/code&gt; as the &lt;code class=&quot;highlighter-rouge&quot;&gt;apiToken&lt;/code&gt; (see the previous section). If your spreadsheet gets into the wrong hands, they will only be able to see the spreadsheet, but they won’t be able to run your Python code. And since you don’t have any credentials in the spreadsheet, you won’t need to rotate them either.&lt;/p&gt;

&lt;p&gt;Importantly, credentials can be properly treated as secrets on your backend, using whatever means your provider offers. You can also control who should have access to the Python code (via Git version control, of course).&lt;/p&gt;

&lt;p&gt;Now that your app is secure, let’s have a look at different ways of running your code.&lt;/p&gt;

&lt;h2 id=&quot;run-your-code-via-buttons-custom-menus-and-triggers&quot;&gt;Run your code via buttons, custom menus, and triggers&lt;/h2&gt;

&lt;p&gt;xlwings can take advantage of the same possibilities that you have when running Google Apps Script code:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Buttons&lt;/strong&gt;: in Google Sheets, you can click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Insert&lt;/code&gt; &amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;Drawing&lt;/code&gt;, draw something that looks like a button (e.g., a rounded rectangle), and hit &lt;code class=&quot;highlighter-rouge&quot;&gt;Save and Close&lt;/code&gt;. Then, right-click on the button so the three dots appear, click on them and select &lt;code class=&quot;highlighter-rouge&quot;&gt;Assign script&lt;/code&gt;, then write the name of the function, in the case of the quickstart sample, this would be &lt;code class=&quot;highlighter-rouge&quot;&gt;hello&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Custom Menus&lt;/strong&gt;: with a few lines of JavaScript code, you can run your function from the menu, see the &lt;a href=&quot;https://developers.google.com/apps-script/guides/menus&quot;&gt;official docs&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Triggers&lt;/strong&gt;: in the Apps Script editor, click on the alarm clock icon in the sidebar on the left. Then, at the bottom-right, click on the blue &lt;code class=&quot;highlighter-rouge&quot;&gt;Add Trigger&lt;/code&gt; button: this will allow you to run your function periodically or based on a spreadsheet action like the open event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are into scientific computing or machine learning, chances are that your buttons and menus will run some pandas code. Let’s have a closer look at how you work with the scientific Python stack in the next section!&lt;/p&gt;

&lt;h2 id=&quot;dataframes-and-plots&quot;&gt;DataFrames and plots&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/google-sheets/pandas.png&quot; alt=&quot;Google Sheets with pandas&quot; /&gt;&lt;/p&gt;

&lt;p&gt;NumPy arrays, pandas DataFrames, and Matplotlib are at the very heart of scientific computing with Python. Accordingly, xlwings supports all of them out of the box. Plotly is also supported, and every other plotting library will also work, as long as the plots can be exported to a picture. The screenshot at the beginning of this section is taken from 
&lt;a href=&quot;https://github.com/xlwings/xlwings-googlesheets-pandas-plots&quot;&gt;https://github.com/xlwings/xlwings-googlesheets-pandas-plots&lt;/a&gt; and as you will see there, writing a DataFrame &lt;code class=&quot;highlighter-rouge&quot;&gt;df&lt;/code&gt; to Google Sheets boils down to:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mysheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Using a Matplotlib plot—as generated by pandas—looks something like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mysheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pictures&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_figure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anchor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;E9&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For more details, have a look at the &lt;a href=&quot;https://github.com/xlwings/xlwings-googlesheets-pandas-plots&quot;&gt;repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the next section, we’ll have a look at how you can deploy these sample apps into production.&lt;/p&gt;

&lt;h2 id=&quot;production-deployment&quot;&gt;Production deployment&lt;/h2&gt;

&lt;p&gt;The number one issue with xlwings has always been the deployment of Python—which is the reason I love this part so much: throw a Docker image or Dockerfile at your favorite service for running Docker containers and you’re done! There are a ton of amazing services in the cloud or under your desk, and you can choose the one that fits your requirements. For more information and a few suggestions with regard to services, see the &lt;a href=&quot;https://docs.xlwings.org/en/stable/remote_interpreter.html#production-deployment&quot;&gt;xlwings docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most of the sample repos mentioned in this post have a Dockerfile included that makes it straightforward to deploy them directly from GitHub. An even easier option is to click the &lt;code class=&quot;highlighter-rouge&quot;&gt;Deploy to Render&lt;/code&gt; button (this will deploy it to their free tier), see e.g., &lt;a href=&quot;https://github.com/xlwings/xlwings-googlesheets-pandas-plots&quot;&gt;https://github.com/xlwings/xlwings-googlesheets-pandas-plots&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s conclude this post with a quick review of the roadmap!&lt;/p&gt;

&lt;h2 id=&quot;roadmap&quot;&gt;Roadmap&lt;/h2&gt;

&lt;p&gt;It’s still early stage for Google Sheets support in xlwings, however, with pandas DataFrames and Python plots alone, you’ll be able to build amazing things! The next steps will be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Add support for custom functions (a.k.a. user-defined functions)&lt;/li&gt;
  &lt;li&gt;Add support for the full xlwings API, including charts, named ranges, etc.&lt;/li&gt;
  &lt;li&gt;Making everything more efficient&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact order will be dictated by you, the user, of course.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The new Google Sheets support in xlwings allows you to build tools in Python that you could previously only build in Google Apps Script, i.e., JavaScript. xlwings allows you to leverage Google’s built-in authentication, you can make the spreadsheet tools secure by keeping the sensitive data out of your spreadsheet, and you have total freedom with regard to the web framework and infrastructure for your backend.&lt;/p&gt;</content><author><name>Felix Zumstein</name></author><category term="xlwings" /><category term="google sheets" /><category term="excel" /><category term="python" /><summary type="html"></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.xlwings.org/assets/images/blog/google-sheets/github.png" /><media:content medium="image" url="https://www.xlwings.org/assets/images/blog/google-sheets/github.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">What Makes Excel’s Lambda Functions so Awesome (and what doesn’t)?</title><link href="https://www.xlwings.org/blog/excel-lambda-functions" rel="alternate" type="text/html" title="What Makes Excel’s Lambda Functions so Awesome (and what doesn’t)?" /><published>2021-02-04T00:00:00+00:00</published><updated>2021-02-04T00:00:00+00:00</updated><id>https://www.xlwings.org/blog/excel-lambda-functions</id><content type="html" xml:base="https://www.xlwings.org/blog/excel-lambda-functions">&lt;p&gt;In December 2020, Microsoft &lt;a href=&quot;https://techcommunity.microsoft.com/t5/excel-blog/announcing-lambda-turn-excel-formulas-into-custom-functions/ba-p/1925546&quot; target=&quot;_blank&quot;&gt;announced Lambda Functions&lt;/a&gt; as a new Excel feature. Lambda functions are an exciting way to define custom functions (a.k.a. user-defined functions) by only relying on Excel formulas, without having to use VBA or writing an add-in. In this blog post, we’re going to see how Lambda Functions work and why they are so awesome. It’s not all roses though: we’ll also look at the current limitations of Lambda Functions and how to solve some of them. Currently, Lambda Functions are only available to Microsoft 365 subscribers on the Beta Channel. To see if you can already use them, start typing &lt;code class=&quot;highlighter-rouge&quot;&gt;=LAMBDA&lt;/code&gt; in a cell and if Excel suggests the function name, you are all set. Would you rather lean back and watch the gist of it in a video? Here you go:&lt;/p&gt;

&lt;lite-youtube videoid=&quot;YWGsYhs5krw&quot; playlabel=&quot;Lambda Functions&quot;&gt;&lt;/lite-youtube&gt;

&lt;h2 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#why-do-we-need-lambda-functions&quot; id=&quot;markdown-toc-why-do-we-need-lambda-functions&quot;&gt;Why Do We Need Lambda Functions?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#create-your-first-lambda-function&quot; id=&quot;markdown-toc-create-your-first-lambda-function&quot;&gt;Create your First Lambda Function!&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-do-lambda-functions-work&quot; id=&quot;markdown-toc-how-do-lambda-functions-work&quot;&gt;How Do Lambda Functions Work?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#why-are-lambda-functions-so-awesome&quot; id=&quot;markdown-toc-why-are-lambda-functions-so-awesome&quot;&gt;Why are Lambda Functions so Awesome&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#limitations-with-lambda-functions&quot; id=&quot;markdown-toc-limitations-with-lambda-functions&quot;&gt;Limitations with Lambda Functions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#managing-lambda-functions-across-workbooks&quot; id=&quot;markdown-toc-managing-lambda-functions-across-workbooks&quot;&gt;Managing Lambda Functions Across Workbooks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#keeping-track-of-your-lambda-functions&quot; id=&quot;markdown-toc-keeping-track-of-your-lambda-functions&quot;&gt;Keeping Track of Your Lambda Functions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;why-do-we-need-lambda-functions&quot;&gt;Why Do We Need Lambda Functions?&lt;/h2&gt;

&lt;p&gt;Let’s say you have an Excel workbook with a list of temperatures in Fahrenheit and Kelvin that you want to transform into Celsius. After looking up the formulas on &lt;a href=&quot;https://en.wikipedia.org/wiki/Conversion_of_units_of_temperature&quot; target=&quot;_blank&quot;&gt;Wikipedia&lt;/a&gt;, chances are you end up writing the following cell formula into cell &lt;code class=&quot;highlighter-rouge&quot;&gt;C2&lt;/code&gt;, before copying it to the cells below:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/lambda-functions/lambda1.png&quot; alt=&quot;lambda1&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This approach has two issues:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If it wasn’t for the header in cell &lt;code class=&quot;highlighter-rouge&quot;&gt;C1&lt;/code&gt;, you wouldn’t necessarily know what that formula is doing, which makes it harder to review and spot errors&lt;/li&gt;
  &lt;li&gt;If you make an error in your formula, let’s say you use &lt;code class=&quot;highlighter-rouge&quot;&gt;9/5&lt;/code&gt; instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;5/9&lt;/code&gt; in the Fahrenheit conversion, you need to search for all formulas across your entire workbook and make sure that every single one is corrected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new Lambda Functions solve both of these issues, let’s see how!&lt;/p&gt;

&lt;h2 id=&quot;create-your-first-lambda-function&quot;&gt;Create your First Lambda Function!&lt;/h2&gt;

&lt;p&gt;Let’s start by defining and using a Lambda Function so we’ll get a feeling for how they work before we’ll get to a more detailed explanation in the next section. In Excel, go to &lt;code class=&quot;highlighter-rouge&quot;&gt;Formulas&lt;/code&gt; &amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;Name Manager&lt;/code&gt; and click on &lt;code class=&quot;highlighter-rouge&quot;&gt;New&lt;/code&gt; (macOS: &lt;code class=&quot;highlighter-rouge&quot;&gt;Formulas&lt;/code&gt; &amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;Define Name&lt;/code&gt;). Fill in the following values; see screenshot:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Name: &lt;code class=&quot;highlighter-rouge&quot;&gt;ToCelsius&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Refers to: &lt;code class=&quot;highlighter-rouge&quot;&gt;=LAMBDA(degrees, source, IF(source = &quot;fahrenheit&quot;, (degrees-32) * (5/9), IF(source = &quot;kelvin&quot;, degrees - 273.15, NA())))&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Comments: Feel free to add comments to explain the parameters and formula, but this is only available on Windows, not macOS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/lambda-functions/lambda2.png&quot; alt=&quot;lambda2&quot; width=&quot;400&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On macOS, the two fields appear right after clicking on &lt;code class=&quot;highlighter-rouge&quot;&gt;Define Name&lt;/code&gt; and have the verbose titles &lt;code class=&quot;highlighter-rouge&quot;&gt;Enter a name for the data range&lt;/code&gt; instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;Name&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;Select the range of cells&lt;/code&gt; instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;Refers to&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Save by clicking on OK and now you’re able to use your new formula like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/lambda-functions/lambda3.png&quot; alt=&quot;lambda1&quot; width=&quot;400&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That’s quite a bit easier to understand, isn’t it? And the best part is that you can make changes to the formula in a single place (via the Name Manager) and it’s fixed everywhere automatically.&lt;/p&gt;

&lt;p&gt;But how do Lambda Functions work exactly? The next section has the details.&lt;/p&gt;

&lt;h2 id=&quot;how-do-lambda-functions-work&quot;&gt;How Do Lambda Functions Work?&lt;/h2&gt;

&lt;p&gt;Lambda is a new function that accepts the following parameters:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LAMBDA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameter1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameter2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;calculation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first n parameters are the parameters of your custom function (you can use a max. of 253 parameters), in our case we use two: &lt;code class=&quot;highlighter-rouge&quot;&gt;degrees&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;source&lt;/code&gt;. The last parameter is the actual custom function, here called &lt;code class=&quot;highlighter-rouge&quot;&gt;calculation&lt;/code&gt;. Being able to refer to explicit parameter names instead of cell references makes the formula already quite a bit easier to understand. Here is, for example, the Fahrenheit conversion:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Without Lambda Functions: &lt;code class=&quot;highlighter-rouge&quot;&gt;IF(B2 = &quot;fahrenheit&quot;, (A2-32) * (5/9)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;In the context of a Lambda Function: &lt;code class=&quot;highlighter-rouge&quot;&gt;IF(source = &quot;fahrenheit&quot;, (degrees-32) * (5/9)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you like, you could try out a Lambda Function directly in an Excel cell like any other formula, but you will have to call it in the very same cell by adding parentheses with the parameters at the end, otherwise, you would get a &lt;code class=&quot;highlighter-rouge&quot;&gt;#CALC!&lt;/code&gt; error. Accordingly, to test our formula without setting it up in the Name Manager, you could write the following in a cell (note the &lt;code class=&quot;highlighter-rouge&quot;&gt;(20, &quot;fahrenheit&quot;)&lt;/code&gt; at the end of the formula which is calling the Lambda Function with these two parameters):&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LAMBDA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;degrees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fahrenheit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;degrees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;kelvin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;degrees&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;273.15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;NA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())))(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fahrenheit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This can be helpful for testing, but you can’t reuse your formula until you assign a name to it in the Name Manager. Now that we have seen how you define and use Lambda Functions, let’s summarize their advantages.&lt;/p&gt;

&lt;h2 id=&quot;why-are-lambda-functions-so-awesome&quot;&gt;Why are Lambda Functions so Awesome&lt;/h2&gt;

&lt;p&gt;As a summary, here is why Lambda Functions are such a big deal:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Users don’t need to learn another language like VBA to write custom functions.&lt;/li&gt;
  &lt;li&gt;This means that you can define custom functions without having to save your Excel workbook in the macro-enabled &lt;code class=&quot;highlighter-rouge&quot;&gt;xlsm&lt;/code&gt; file—not having to click on the &lt;code class=&quot;highlighter-rouge&quot;&gt;Enable Macros&lt;/code&gt; button will make everybody’s life easier.&lt;/li&gt;
  &lt;li&gt;Using Lambda Function means that you change formulas in exactly one spot and it’s going to be changed everywhere you use it. This is a massive improvement in terms of making your workbook less error-prone.&lt;/li&gt;
  &lt;li&gt;Instead of long, difficult-to-guess formulas, you can use short, descriptive names for your custom functions which makes it much easier to understand your workbook—especially for others!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the advantages covered, let’s not forget about the current limitations and downsides of Lambda Functions.&lt;/p&gt;

&lt;h2 id=&quot;limitations-with-lambda-functions&quot;&gt;Limitations with Lambda Functions&lt;/h2&gt;

&lt;p&gt;To be fair: Lambda Functions are brand new, so it’s expected that there are still a few rough edges. Microsoft also explicitly mentions on their &lt;a href=&quot;https://techcommunity.microsoft.com/t5/excel-blog/announcing-lambda-turn-excel-formulas-into-custom-functions/ba-p/1925546&quot; target=&quot;_blank&quot;&gt;announcement&lt;/a&gt; that this initial release is just the beginning and that they’ll look into improving the functionality based on user feedback going forward. Accordingly, the following points should be understood as constructive feedback:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;To start with the obvious: everybody you work with will need the latest version of Microsoft Excel to get access to Lambda Functions.&lt;/li&gt;
  &lt;li&gt;Lambda Functions are hidden away: it may not be instantly obvious to a user why two seemingly same workbooks can yield different values. This could happen if you define the &lt;code class=&quot;highlighter-rouge&quot;&gt;ToCelsius&lt;/code&gt; formula in one workbook with an error and in another one correctly.&lt;/li&gt;
  &lt;li&gt;This is related to the previous point: Lambda Function can’t be reused across different workbooks which is—in my view—their biggest limitation (but I’ll show you a workaround below).&lt;/li&gt;
  &lt;li&gt;All parameters of a Lambda Function are optional, which becomes an issue if you just want to provide the 7th parameter in a function with 10 parameters: &lt;code class=&quot;highlighter-rouge&quot;&gt;=MyFunction(,,,,,,&quot;some argument&quot;,,,)&lt;/code&gt;. Good luck figuring out which argument that is! It would help, if Lambda Functions would accept &lt;em&gt;named parameters&lt;/em&gt; similar to how VBA does it, so you could write instead &lt;code class=&quot;highlighter-rouge&quot;&gt;=MyFunction(some_parameter:=&quot;some argument&quot;)&lt;/code&gt; without having to rely on an argument’s position.&lt;/li&gt;
  &lt;li&gt;With Lambda Functions, you don’t get a tooltip that shows you which argument is which, something that you are used to from the built-in functions. I really hope this will be added later on so they will become easier to use.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It’s very hard to type in a formula into the Name Manager (and macOS doesn’t even have a proper Name Manager!). Even though our &lt;code class=&quot;highlighter-rouge&quot;&gt;ToCelsius&lt;/code&gt; function is very simple, even just looking for a typo is nothing else than awkward. So please, Microsoft, give us a proper editor with (a) multiple lines, (b) a monospaced font, and (c) syntax highlighting to edit these functions (something that would be useful for normal functions, too, by the way). Seeing the formula capped to 40 characters in the Name Manager is no fun:&lt;/p&gt;

    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  =LAMBDA(degrees, source, IF(source = &quot;fa
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;Instead, a proper formula editor would show it like this, which gives you a chance to actually understand what’s going on:&lt;/p&gt;

    &lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LAMBDA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;degrees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
         &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fahrenheit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;degrees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;nf&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;kelvin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;degrees&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;273.15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;NA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these issues have a solution: the next section continues by showing you how to work around the inability of sharing Lambda Functions across workbooks.&lt;/p&gt;

&lt;h2 id=&quot;managing-lambda-functions-across-workbooks&quot;&gt;Managing Lambda Functions Across Workbooks&lt;/h2&gt;

&lt;p&gt;I am a Python guy, so this is the tool I use to solve pretty much all my issues in life, including the one that my Lambda Functions from one workbook don’t magically appear in all my other workbooks where I want them. I won’t be covering how to get started with Python in this blog post, but there are enough resources on the web in case you are a Python newbie (downloading the &lt;a href=&quot;https://www.anaconda.com/products/individual&quot;&gt;Anaconda&lt;/a&gt; or &lt;a href=&quot;https://winpython.github.io&quot;&gt;WinPython&lt;/a&gt; distribution is usually a good place to start—if you use a different distro, just make sure to run &lt;code class=&quot;highlighter-rouge&quot;&gt;pip install xlwings&lt;/code&gt; to install the xlwings package). At a later point, I might also consider turning this into a little command line application to make it easier to use. Anyhow, for now, create the following file and call it &lt;code class=&quot;highlighter-rouge&quot;&gt;lambda_report.py&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# lambda_report.py
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pathlib&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;xlwings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# TODO: adjust the DIRECTORY path
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DIRECTORY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;C:\Users\felix\Desktop\lambda&apos;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DIRECTORY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rglob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;[!~$]*.xls*&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;------ &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ------&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;refers_to&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;refers_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;_xlfn.&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;_xlpm.&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lambda_functions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refers_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;startswith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;=lambda&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;lambda_functions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;refers_to&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambda_functions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambda_functions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Make sure to update the path for &lt;code class=&quot;highlighter-rouge&quot;&gt;DIRECTORY&lt;/code&gt; and run it on your Anaconda Prompt or Command Prompt on Windows or Terminal on macOS as follows (or use your favorite Python editor):&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python lambda_report.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will print all Lambda Functions for all Excel workbooks in the indicated &lt;code class=&quot;highlighter-rouge&quot;&gt;DIRECTORY&lt;/code&gt; including its sub-directories like so (the Lambda Functions from &lt;code class=&quot;highlighter-rouge&quot;&gt;test_book.xlsx&lt;/code&gt; are taken from the &lt;a href=&quot;https://support.microsoft.com/en-us/office/lambda-function-bd212d27-1cd1-4321-a34a-ccbf254b8b67&quot; target=&quot;_blank&quot;&gt;official documentation&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;------ C:\Users\felix\Desktop\lambda\temperature.xlsx ------
ToCelsius: =LAMBDA(degrees,source, IF(source = &quot;fahrenheit&quot;, (degrees-32) * (5/9), IF(source = &quot;kelvin&quot;, degrees - 273.15, NA())))

------ C:\Users\felix\Desktop\lambda\subfolder\test_book.xlsx ------
Hypotenuse: =LAMBDA(a, b, SQRT((a^2+b^2)))
CountWords: =LAMBDA(text, LEN(TRIM(text)) - LEN(SUBSTITUTE(TRIM(text), &quot; &quot;, &quot;&quot;)) + 1)
ThanksgivingDate: =LAMBDA(year, TEXT(DATE(year, 11, CHOOSE(WEEKDAY(DATE(year, 11, 1)), 26, 25, 24, 23, 22, 28, 27)), &quot;mm/dd/yyyy&quot;))

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now that you have your report and know which workbooks contain which Lambda Functions, you may want to add or correct a couple of Lambda Functions across a bunch of workbooks. Here is how this works: create a new Python file called &lt;code class=&quot;highlighter-rouge&quot;&gt;add_lambdas.py&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# add_lambdas.py
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pathlib&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;xlwings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# TODO: adjust the DIRECTORY path and the LAMBDAS you want to add
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DIRECTORY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;C:\Users\felix\Desktop\lambda&apos;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;LAMBDAS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Hypotenuse&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=LAMBDA(a, b, SQRT((a^2+b^2)))&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;CountWords&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=LAMBDA(text, LEN(TRIM(text)) - LEN(SUBSTITUTE(TRIM(text), &quot; &quot;, &quot;&quot;)) + 1)&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DIRECTORY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rglob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;[!~$]*.xls*&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Adding Lambda Functions to: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LAMBDAS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refers_to&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refers_to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;refers_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Again, make sure to update &lt;code class=&quot;highlighter-rouge&quot;&gt;DIRECTORY&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;LAMBDAS&lt;/code&gt; to suit your needs, then run the script like this:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python add_lambdas.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will add the two Lambda Functions &lt;code class=&quot;highlighter-rouge&quot;&gt;Hypotenuse&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;CountWords&lt;/code&gt; to all your Excel files in the specified &lt;code class=&quot;highlighter-rouge&quot;&gt;DIRECTORY&lt;/code&gt; and its subdirectories—if the name already exists, it will be replaced.&lt;/p&gt;

&lt;p&gt;Now that we’ve seen an easy way how to manage your Lambda Functions across workbooks, let’s learn about how we can keep track of them and make sure that nobody messes with our Lambada Functions!&lt;/p&gt;

&lt;h2 id=&quot;keeping-track-of-your-lambda-functions&quot;&gt;Keeping Track of Your Lambda Functions&lt;/h2&gt;

&lt;p&gt;When you use Lambda Functions, you have your critical business logic sitting in single-line formulas of which you can only see about 40 characters at a time in the Name Manager. This doesn’t exactly make it easy to review your formulas. If you make a change to a Lambda Function, it’s easy to introduce errors (especially because Excel constantly thinks you want to reference cells in the &lt;code class=&quot;highlighter-rouge&quot;&gt;Refers To&lt;/code&gt; section), so you better watch your back!&lt;/p&gt;

&lt;p&gt;To keep the upper hand in software development, professional coders use a version control system like Git, usually through a provider like GitHub, GitLab, or BitBucket. Git keeps track of code changes and allows team members to perform a peer review by highlighting changes (think of it as the track changes functionality in Word). Git, however, has two issues when you use it with Excel files:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Git doesn’t understand Excel files and isn’t able to show you their contents or changes&lt;/li&gt;
  &lt;li&gt;Git has a steep learning curve that doesn’t really appeal to spreadsheet users, as the vast majority of them are business users, not software developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still, we believe that version control is not something that you should ignore just because you use Excel, which is the reason why we built &lt;a href=&quot;https://www.xltrail.com&quot;&gt;xltrail&lt;/a&gt;: xltrail is a Git system that is easy to use for business users via drag and drop and knows how to deal with Excel files. Let’s see how xltrail keeps track of Lambda Functions by looking at the Excel file of the first sample of this blog post:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/lambda-functions/lambda4.png&quot; alt=&quot;lambda4&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Clicking on &lt;code class=&quot;highlighter-rouge&quot;&gt;Sheet1&lt;/code&gt; will give you the same view as the very first screenshot in this blog post, but you will see the formulas:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/lambda-functions/lambda5.png&quot; alt=&quot;lambda5&quot; width=&quot;400&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But let’s not get sidetracked here—after all, I wanted to show you how Lambda Functions are tracked. When click on &lt;code class=&quot;highlighter-rouge&quot;&gt;DefinedNames&lt;/code&gt; instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;Sheet1&lt;/code&gt;, you will see all your Lambda Functions:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/lambda-functions/lambda6.png&quot; alt=&quot;lambda6&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now, the right sidebar doesn’t just give you access to past versions but most importantly, you can click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Show changes&lt;/code&gt; to see what changed:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/lambda-functions/lambda7.png&quot; alt=&quot;lambda7&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This allows you to verify that I have indeed properly fixed the formula for the Fahrenheit conversion (red is the previous version and green is the current version) and you know why you’re getting wrong results when you use an older version of this workbook. If you want to try this out on your own, &lt;a href=&quot;https://www.xltrail.com/&quot; target=&quot;_blank&quot;&gt;sign up for a free trial&lt;/a&gt;!&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;My goal with this blog post was to show you what Lambda Functions are, why they are great and how you use them in practice. Unlike many other blog posts though, I didn’t leave it at this but moved on by looking at the challenges that come with Lambda Functions, my main critique being that they are hard to share and keep track of—especially since the Name Manager isn’t a good formula editor.&lt;/p&gt;

&lt;p&gt;Given how ecstatic Lambda Functions have been received by the community, I am pretty sure that Microsoft will make them better over time, so I do hope that we will get a proper formula editor and a way to share Lambda Functions between workbooks. In the meantime, you can always use my short Python scripts to keep your Lambda Functions under control with a minimum of fuss!&lt;/p&gt;</content><author><name>Felix Zumstein</name></author><category term="xlwings" /><category term="excel" /><category term="python" /><category term="lambda functions" /><summary type="html">In December 2020, Microsoft announced Lambda Functions as a new Excel feature. Lambda functions are an exciting way to define custom functions (a.k.a. user-defined functions) by only relying on Excel formulas, without having to use VBA or writing an add-in. In this blog post, we’re going to see how Lambda Functions work and why they are so awesome. It’s not all roses though: we’ll also look at the current limitations of Lambda Functions and how to solve some of them. Currently, Lambda Functions are only available to Microsoft 365 subscribers on the Beta Channel. To see if you can already use them, start typing =LAMBDA in a cell and if Excel suggests the function name, you are all set. Would you rather lean back and watch the gist of it in a video? Here you go:</summary></entry><entry><title type="html">5 Python Libraries for Reporting and Factsheets</title><link href="https://www.xlwings.org/blog/reporting-with-python" rel="alternate" type="text/html" title="5 Python Libraries for Reporting and Factsheets" /><published>2020-06-03T00:00:00+00:00</published><updated>2020-06-03T00:00:00+00:00</updated><id>https://www.xlwings.org/blog/reporting-with-python</id><content type="html" xml:base="https://www.xlwings.org/blog/reporting-with-python">&lt;p&gt;Python is a popular tool for all kind of automation needs and therefore a great candidate for your reporting tasks.
There is a wealth of techniques and libraries available and we’re going to introduce five popular options here. After reading this blog post, you should be able to pick the right library for your next reporting project according to your needs and skill set.&lt;/p&gt;

&lt;h2 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#overview&quot; id=&quot;markdown-toc-overview&quot;&gt;Overview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#pandas&quot; id=&quot;markdown-toc-pandas&quot;&gt;Pandas&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#xlwings&quot; id=&quot;markdown-toc-xlwings&quot;&gt;xlwings&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#plotly-dash&quot; id=&quot;markdown-toc-plotly-dash&quot;&gt;Plotly Dash&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#datapane&quot; id=&quot;markdown-toc-datapane&quot;&gt;Datapane&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#reportlab&quot; id=&quot;markdown-toc-reportlab&quot;&gt;ReportLab&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;Before we begin, here is a high level comparison of the libraries presented in this post:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Library&lt;/th&gt;
      &lt;th&gt;Technology&lt;/th&gt;
      &lt;th&gt;Summary&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://pandas.pydata.org/&quot; target=&quot;_blank&quot;&gt;Pandas&lt;/a&gt; + HTML&lt;/td&gt;
      &lt;td&gt;HTML&lt;/td&gt;
      &lt;td&gt;You can generate beautiful reports in the form of static web pages if you know your way around HTML + CSS. The HTML report can also be turned into a PDF for printing.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://pandas.pydata.org/&quot; target=&quot;_blank&quot;&gt;Pandas&lt;/a&gt; + Excel&lt;/td&gt;
      &lt;td&gt;Excel&lt;/td&gt;
      &lt;td&gt;This is a great option if the report has to be in Excel. It can be run on a server where Excel is not installed, i.e. it’s an ideal candidate for a “download to Excel” button in a web app. The Excel file can be exported to PDF.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.xlwings.org/&quot;&gt;xlwings&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Excel&lt;/td&gt;
      &lt;td&gt;xlwings allows the use of an Excel template so the formatting can be done by users without coding skills. It requires, however, an installation of Excel so it’s a good option when the report can be generated on a desktop, e.g. for ad-hoc reports. The Excel file can be exported to PDF.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://plot.ly/dash/&quot; target=&quot;_blank&quot;&gt;Plotly Dash&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;HTML&lt;/td&gt;
      &lt;td&gt;Dash allows you to easily spin up a great looking web dashboard that is interactive without having to write any JavaScript code. If formatted properly, it can be used as a source for PDFs, too. Like Pandas + HTML, it requires good HTML + CSS skills to make it look the way you want.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://datapane.com/&quot; target=&quot;_blank&quot;&gt;Datapane&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;HTML&lt;/td&gt;
      &lt;td&gt;Datapane allows you to create HTML reports with interactive elements. It also offers a hosted solution so end users can change the input parameters that are used to create these reports.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.reportlab.com/&quot; target=&quot;_blank&quot;&gt;ReportLab&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;PDF&lt;/td&gt;
      &lt;td&gt;ReportLab creates direct PDF files without going through HTML or Excel first. It’s very fast and powerful but comes with a steep learning curve. Used by Wikipedia for their PDF export.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;pandas&quot;&gt;Pandas&lt;/h2&gt;

&lt;p&gt;I am probably not exaggerating when I claim that almost all reporting in Python starts with &lt;a href=&quot;https://pandas.pydata.org/&quot; target=&quot;_blank&quot;&gt;Pandas&lt;/a&gt;. It’s incredibly easy to create  Pandas DataFrames with data from databases, Excel and csv files or json responses from a web API. Once you have the raw data in a DataFrame, it only requires a few lines of code to clean the data and slice &amp;amp; dice it into a digestible form for reporting. Accordingly, Pandas will be used in all sections of this blog post, but we’ll start by leveraging the built-in capabilities that Pandas offers for reports in Excel and HTML format.&lt;/p&gt;

&lt;h3 id=&quot;pandas--excel&quot;&gt;Pandas + Excel&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Required libraries&lt;/strong&gt;: &lt;code class=&quot;highlighter-rouge&quot;&gt;pandas&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;xlsxwriter&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you want to do something slightly more sophisticated than just dumping a DataFrame into an Excel spreadsheet, I found that Pandas and &lt;a href=&quot;https://xlsxwriter.readthedocs.io/&quot; target=&quot;_blank&quot;&gt;XlsxWriter&lt;/a&gt; is the easiest combination, but others may prefer &lt;a href=&quot;https://openpyxl.readthedocs.io/&quot; target=&quot;_blank&quot;&gt;OpenPyXL&lt;/a&gt;. In that case you should be able to easily adopt this snippet by replacing &lt;code class=&quot;highlighter-rouge&quot;&gt;engine=&apos;xlsxwriter&apos;&lt;/code&gt; with &lt;code class=&quot;highlighter-rouge&quot;&gt;engine=&apos;openpyxl&apos;&lt;/code&gt; and changing the book/sheet syntax so it works with OpenPyXL:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pandas&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Sample DataFrame
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;one&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;two&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;three&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;four&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;b&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;d&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;e&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Dump Pandas DataFrame to Excel sheet
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ExcelWriter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;myreport.xlsx&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;engine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;xlsxwriter&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_excel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sheet_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Sheet1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;startrow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Get book and sheet objects for futher manipulation below
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;book&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sheets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Sheet1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Title
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bold&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;bold&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;size&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;A1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;My Report&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Color negative values in the DataFrame in red
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;font_color&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;#E93423&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conditional_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;B4:E8&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;type&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;cell&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;criteria&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&amp;lt;=&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;value&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;format&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Chart
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;type&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;column&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;values&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=Sheet1!B4:B8&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=Sheet1!B3&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;categories&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=Sheet1!$A$4:$A$8&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;values&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=Sheet1!C4:C8&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=Sheet1!C3&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;values&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=Sheet1!D4:D8&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=Sheet1!D3&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;values&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=Sheet1!E4:E8&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;=Sheet1!E3&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;insert_chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;A10&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running this will produce the following report:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/reporting/pandas_excel.png&quot; alt=&quot;Pandas Excel&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Of course, we could now go back to the script and add more code to style it a bit nicer, but I leave this as an exercise to the reader…&lt;/p&gt;

&lt;h3 id=&quot;pandas--html&quot;&gt;Pandas + HTML&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Required libraries&lt;/strong&gt;: &lt;code class=&quot;highlighter-rouge&quot;&gt;pandas&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;jinja2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Creating an HTML report with pandas works similar to what’ve just done with Excel: If you want a tiny bit more than just dumping a DataFrame as a raw HTML table, then you’re best off by combining Pandas with a templating engine like &lt;a href=&quot;https://jinja.palletsprojects.com/&quot; target=&quot;_blank&quot;&gt;Jinja&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;First, let’s create a file called &lt;code class=&quot;highlighter-rouge&quot;&gt;template.html&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;20px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nl&quot;&gt;border-collapse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;collapse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nl&quot;&gt;border-bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;th&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;20px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My Report&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

{{ my_table }}

&lt;span class=&quot;nt&quot;&gt;&amp;lt;img&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;plot.svg&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;width=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;600&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, in the same directory, let’s run the following Python script that will create our HTML report:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pandas&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;jinja2&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Sample DataFrame
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;one&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;two&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;three&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;four&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;b&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;d&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;e&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# See: https://pandas.pydata.org/pandas-docs/stable/user_guide/style.html#Building-styles
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;color_negative_red&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;red&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;black&apos;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;color: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;styler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;applymap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;color_negative_red&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Template handling
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jinja2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jinja2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FileSystemLoader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;searchpath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;template.html&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my_table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;styler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Plot
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_figure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;savefig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;plot.svg&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Write the HTML file
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;report.html&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;w&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The result is a nice looking HTML report that could also be printed as a PDF by using something like &lt;a href=&quot;https://weasyprint.org/&quot; target=&quot;_blank&quot;&gt;WeasyPrint&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/reporting/pandas_html.png&quot; alt=&quot;Pandas Excel&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note that for such an easy example, you wouldn’t necessarily need to use a Jinja template. But when things start to become more complex, it’ll definitely come in very handy.&lt;/p&gt;

&lt;h2 id=&quot;xlwings&quot;&gt;xlwings&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.xlwings.org&quot;&gt;xlwings&lt;/a&gt; allows you to program and automate Excel with Python instead of VBA. The difference to XlsxWriter or OpenPyXL (used above in the Pandas section) is the following: XlsxWriter and OpenPyXL write Excel files directly on disk. They work wherever Python works and don’t require an installation of Microsoft Excel.
xlwings, on the other hand, can write, read and edit Excel files via the Excel application, i.e. a local installation of Microsoft Excel is required. xlwings also allows you to create macros and user-defined functions in Python rather than in VBA, but for reporting purposes, we won’t really need that.&lt;/p&gt;

&lt;p&gt;While XlsxWriter/OpenPyXL are the best choice if you need to produce reports in a scalable way on your Linux web server, xlwings does have the advantage that it can edit pre-formatted Excel files without losing or destroying anything. OpenPyXL on the other hand (the only writer library with xlsx editing capabilities) will drop some formatting and sometimes leads to Excel raising errors during further manual editing.&lt;/p&gt;

&lt;h3 id=&quot;xlwings-open-source&quot;&gt;xlwings (Open Source)&lt;/h3&gt;

&lt;p&gt;Replicating the sample we had under Pandas is easy enough with the open-source version of xlwings:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;xlwings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pandas&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Open a template file
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;mytemplate.xlsx&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Sample DataFrame
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;one&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;two&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;three&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;four&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;b&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;d&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;e&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Assign data to cells
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sheets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;A1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;My Report&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;wb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sheets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;A3&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Save under a new file name
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;myreport.xlsx&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running this will produce the following report:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/reporting/xlwings_ce.png&quot; alt=&quot;xlwings CE&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So where does all the formatting come from? The formatting is done directly in the Excel template before running the script. This means that instead of having to program tens of lines of code to format a single cell with the proper font, colors and borders, I can just make a few clicks in Excel. xlwings then merely opens the template file and inserts the values.&lt;/p&gt;

&lt;p&gt;This allows us to create a good looking report in your corporate design very fast. The best part is that the Python developer doesn’t necessarily have to do the formatting but can leave it to the business user who owns the report.&lt;/p&gt;

&lt;p&gt;Note that you could instruct xlwings to run the report in a separate and hidden instance of Excel so it doesn’t interfere with your other work.&lt;/p&gt;

&lt;h3 id=&quot;xlwings-pro&quot;&gt;xlwings PRO&lt;/h3&gt;

&lt;p&gt;The Pandas + Excel as well as the xlwings (open source) sample both have a few issues:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If, for example, you insert a few rows below the title, you will have to adjust the cell references accordingly in the Python code. Using named ranges could help but they have other limitations (like the one mentioned at the end of this list).&lt;/li&gt;
  &lt;li&gt;The number of rows in the table might be dynamic. This leads to two issues: (a) data rows might not be formatted consistently and (b) content below the table might get overwritten if the table is too long.&lt;/li&gt;
  &lt;li&gt;Placing the same value in a lot of different cells (e.g. a date in the source note of every table or chart) will cause duplicated code or unnecessary loops.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To fix these issues, xlwings PRO comes with a dedicated reports package:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Separation of code and design&lt;/strong&gt;: Users without coding skills can change the template on their own without having to touch the Python code.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Template variables&lt;/strong&gt;: Python variables (between double curly braces) can be directly used in cells , e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;{{ title }}&lt;/code&gt;. They act as placeholders that will be replaced by the values of the variables.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Frames for dynamic tables&lt;/strong&gt;: Frames are vertical containers that dynamically align and style tables that have a variable number of rows. To see how Frames work, have a look at the &lt;a href=&quot;https://docs.xlwings.org/en/stable/reports.html#frames&quot; target=&quot;_blank&quot;&gt;documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can get a free trial for xlwings PRO &lt;a href=&quot;https://www.xlwings.org/trial&quot;&gt;here&lt;/a&gt;. When using the xlwings PRO reports package, your code simplifies to the following:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pandas&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;xlwings.pro.reports&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_report&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# part of xlwings PRO
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Sample DataFrame
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;one&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;two&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;three&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;four&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;b&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;d&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;e&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Create the report by passing in all variables as kwargs
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;mytemplate.xlsx&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;s&quot;&gt;&apos;myreport.xlsx&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;My Report&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All that’s left is to create a template with the placeholders for &lt;code class=&quot;highlighter-rouge&quot;&gt;title&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;df&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/reporting/xlwings_pro.png&quot; alt=&quot;xlwings PRO&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Running the script will produce the same report that we generated with the open source version of xlwings above. The beauty of this approach is that there are no hard coded cell references anymore in your Python code. This means that the person who is responsible for the layout can move the placeholders around and change the fonts and colors without having to bug the Python developer anymore.&lt;/p&gt;

&lt;h2 id=&quot;plotly-dash&quot;&gt;Plotly Dash&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Required libraries&lt;/strong&gt;: &lt;code class=&quot;highlighter-rouge&quot;&gt;pandas&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;dash&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Plotly is best known for their beautiful and open-source &lt;a href=&quot;https://plot.ly/graphing-libraries/&quot; target=&quot;_blank&quot;&gt;JavaScript charting library&lt;/a&gt; which builds the core of &lt;a href=&quot;https://plot.ly/chart-studio/&quot; target=&quot;_blank&quot;&gt;Chart Studio&lt;/a&gt;, a platform for collaboratively designing charts (no coding required).&lt;/p&gt;

&lt;p&gt;To create a report though, we’re using their latest product &lt;a href=&quot;https://plot.ly/dash/&quot; target=&quot;_blank&quot;&gt;Plotly Dash&lt;/a&gt;, an open-source framework that allows the creation of interactive web dashboards with Python only (no need to write JavaScript code). Plotly Dash is also available as &lt;a href=&quot;https://plot.ly/get-pricing/&quot; target=&quot;_blank&quot;&gt;Enterprise plan&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How it works is best explained by looking at some code, adopted with minimal changes from the official getting started guide:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pandas&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;dash&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;dash_core_components&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dcc&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;dash_html_components&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;dash.dependencies&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Output&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Sample DataFrame
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Dash app - The CSS code is pulled in from an external file
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Dash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;external_stylesheets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;https://codepen.io/chriddyp/pen/bWLwgP.css&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# This defines the HTML layout
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;H1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;My Report&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dcc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;graph-with-slider&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dcc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Slider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;year-slider&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;year&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;year&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;year&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;year&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()},&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# This code runs every time the slider below the chart is changed
&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;graph-with-slider&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;figure&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;year-slider&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;value&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update_figure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;selected_year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;filtered_df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;selected_year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;traces&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filtered_df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;df_by_continent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filtered_df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filtered_df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;continent&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;traces&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df_by_continent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;gdpPercap&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df_by_continent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;lifeExp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df_by_continent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;country&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;markers&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;size&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;line&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;width&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;color&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;white&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}},&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&apos;data&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;traces&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&apos;layout&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;xaxis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;type&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;log&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;title&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;GDP Per Capita&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;range&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;4.8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]},&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;yaxis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;title&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;Life Expectancy&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;range&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]},&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;l&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;b&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;t&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;r&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;x&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;y&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;hovermode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;closest&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;duration&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;__main__&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run_server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running this script and navigating to &lt;a href=&quot;http://localhost:8050&quot;&gt;http://localhost:8050&lt;/a&gt; in your browser will give you this dashboard:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/reporting/plotly.png&quot; alt=&quot;Plotly&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The charts look great by default and it’s very easy to make your dashboard interactive by writing simple callback functions in Python: You can choose the year by clicking on the slider below the chart. In the background, every change to our &lt;code class=&quot;highlighter-rouge&quot;&gt;year-slider&lt;/code&gt; will trigger the &lt;code class=&quot;highlighter-rouge&quot;&gt;update_figure&lt;/code&gt; callback function and hence update the chart.&lt;/p&gt;

&lt;p&gt;By arranging your documents properly, you could create an interactive web dashboard that can also act as the source for your PDF factsheet, see for example their &lt;a href=&quot;https://dash-gallery.plotly.host/dash-financial-report/&quot; target=&quot;_blank&quot;&gt;financial factsheet demo&lt;/a&gt; together with it’s &lt;a href=&quot;https://github.com/plotly/dash-sample-apps/tree/master/apps/dash-financial-report&quot; target=&quot;_blank&quot;&gt;source code&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;alternatives-to-plotly-dash&quot;&gt;Alternatives to Plotly Dash&lt;/h3&gt;

&lt;p&gt;If you are looking for an alternative to Plotly Dash, make sure to check out &lt;a href=&quot;https://panel.holoviz.org/&quot; target=&quot;_blank&quot;&gt;Panel&lt;/a&gt;. Panel was originally developed with the support of &lt;a href=&quot;https://www.anaconda.com/&quot; target=&quot;_blank&quot;&gt;Anaconda Inc.&lt;/a&gt;, and is now maintained by Anaconda developers and community contributors. Unlike Plotly Dash, Panel is very inclusive and supports a wide range of plotting libraries  including: &lt;a href=&quot;https://bokeh.org/&quot; target=&quot;_blank&quot;&gt;Bokeh&lt;/a&gt;, &lt;a href=&quot;https://altair-viz.github.io/&quot; target=&quot;_blank&quot;&gt;Altair&lt;/a&gt;, &lt;a href=&quot;https://matplotlib.org/&quot; target=&quot;_blank&quot;&gt;Matplotlib&lt;/a&gt; and others (including also Plotly).&lt;/p&gt;

&lt;h2 id=&quot;datapane&quot;&gt;Datapane&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Required libraries&lt;/strong&gt;: &lt;code class=&quot;highlighter-rouge&quot;&gt;datapane&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://datapane.com&quot;&gt;Datapane&lt;/a&gt; is a framework for reporting which allows you to generate interactive reports from pandas DataFrames, Python visualisations (such as Bokeh and Altair), and Markdown. Unlike solutions such as Dash, Datapane allows you to generate standalone reports which don’t require a running Python server—but it doesn’t require any HTML coding either.&lt;/p&gt;

&lt;p&gt;Using Datapane, you can either generate one-off reports, or deploy your Jupyter Notebook or Python script so others can generate reports dynamically by entering parameters through an automatically generated web app.&lt;/p&gt;

&lt;h3 id=&quot;datapane-open-source-library&quot;&gt;Datapane (open-source library)&lt;/h3&gt;

&lt;p&gt;Datapane’s open-source library allows you to create reports from components, such as a &lt;code class=&quot;highlighter-rouge&quot;&gt;Table&lt;/code&gt; component, a &lt;code class=&quot;highlighter-rouge&quot;&gt;Plot&lt;/code&gt; component, etc. These components are compatible with Python objects such as pandas DataFrames, and many visualisation libraries, such as Altair:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;datapane&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pandas&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;altair&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alt&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;https://query1.finance.yahoo.com/v7/finance/download/GOOG?period2=1585222905&amp;amp;interval=1mo&amp;amp;events=history&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Date:T&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Open&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mark_line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interactive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;report.html&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code renders a standalone HTML document with an interactive, searchable table and plot component.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/reporting/datapane1.png&quot; alt=&quot;datapane1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you want to publish your report, you can login to Datapane (via &lt;code class=&quot;highlighter-rouge&quot;&gt;$ datapane login&lt;/code&gt;) and use the &lt;code class=&quot;highlighter-rouge&quot;&gt;publish&lt;/code&gt; method, which will give you a URL &lt;a href=&quot;https://datapane.com/leo/reports/stock_report_5d2925b9/&quot;&gt;such as this&lt;/a&gt; which you can share or embed.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;my_report&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;hosted-reporting-apps&quot;&gt;Hosted Reporting Apps&lt;/h3&gt;

&lt;p&gt;Datapane can also be used to deploy Jupyter Notebooks and Python scripts so that other people who are not familiar with Python can generate custom reports. By adding a YAML file to your folder, you can specify input parameters as well as dependencies (through pip, Docker, or local folders). Datapane also has support for managing secret variables, such as database passwords, and for storing and persisting files. Here is a sample script (&lt;code class=&quot;highlighter-rouge&quot;&gt;stocks.py&lt;/code&gt;) and YAML file (&lt;code class=&quot;highlighter-rouge&quot;&gt;stocks.yaml&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# stocks.py
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;datapane&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;altair&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alt&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;yfinance&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yf&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load_defaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;./stocks.yaml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;tickers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;tickers&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plot_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;plot_type&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;period&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;period&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;download&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tickers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tickers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupby&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;ticker&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Close&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reset_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;melt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Date&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;symbol&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;price&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;base_chart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Date:T&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;price:Q&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;symbol&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interactive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mark_line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plot_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;line&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mark_bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;stock_report&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headline&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Report on &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tickers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# stocks.yaml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;stock_analysis&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;stocks.py&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Script parameters&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tickers&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;A list of tickers to plot&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;list&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;GOOG&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;MSFT&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;IBM&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;period&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Time period to plot&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;enum&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;choices&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1d&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;5d&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1mo&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3mo&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;6mo&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1y&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2y&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;5y&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;10y&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ytd&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;max&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1mo&apos;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;plot_type&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;enum&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;line&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;choices&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bar&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;line&apos;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;## Python packages required for the script&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;requirements&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yfinance&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/reporting/datapane2.png&quot; alt=&quot;datapane2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Publishing this into a reporting app is easy as running &lt;code class=&quot;highlighter-rouge&quot;&gt;$ datapane script deploy&lt;/code&gt;. For a full example see &lt;a href=&quot;https://github.com/datapane/gallery/blob/master/stock-reporting/&quot; target=&quot;_blank&quot;&gt;this example GitHub repository&lt;/a&gt; or &lt;a href=&quot;https://docs.datapane.com&quot; target=&quot;_blank&quot;&gt;read the docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;reportlab&quot;&gt;ReportLab&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Required libraries&lt;/strong&gt;: &lt;code class=&quot;highlighter-rouge&quot;&gt;pandas&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;reportlab&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.reportlab.com/&quot; target=&quot;_blank&quot;&gt;ReportLab&lt;/a&gt; writes PDF files directly. Most prominently, Wikipedia uses ReportLab to generate their PDF exports. One of the key strength of ReportLab is that it builds PDF reports “at incredible speeds”, to cite their homepage. Let’s have a look at some sample code for both the open-source and the commercial version!&lt;/p&gt;

&lt;h3 id=&quot;reportlab-opensource&quot;&gt;ReportLab OpenSource&lt;/h3&gt;

&lt;p&gt;In its most basic functionality, ReportLab uses a canvas where you can place objects using a coordinate system:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;reportlab.pdfgen&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;canvas&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;canvas&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Canvas&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello.pdf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drawString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;800&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ReportLab also offers an advanced mode called PLATYPUS (Page Layout and Typography Using Scripts), which is able to define dynamic layouts based on templates at the document and page level. Within pages, &lt;code class=&quot;highlighter-rouge&quot;&gt;Frames&lt;/code&gt; would then arrange &lt;code class=&quot;highlighter-rouge&quot;&gt;Flowables&lt;/code&gt; (e.g. text and pictures) dynamically according to their height. Here is a very basic example of how you put PLATYPUS at work:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pandas&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;reportlab.pdfgen.canvas&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Canvas&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;reportlab.lib&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colors&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;reportlab.lib.styles&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getSampleStyleSheet&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;reportlab.lib.units&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inch&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;reportlab.platypus&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Paragraph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Spacer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TableStyle&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Sample DataFrame
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;one&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;two&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;three&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;four&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;b&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;d&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;e&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Style Table
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reset_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;index&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tolist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TableStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;INNERGRID&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;BOX&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Components that will be passed into a Frame
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;story&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Paragraph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;My Report&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getSampleStyleSheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Heading1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
         &lt;span class=&quot;n&quot;&gt;Spacer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Use a Frame to dynamically align the compents and write the PDF file
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Canvas&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;report.pdf&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addFromList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;story&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running this script will produce the following PDF:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/reporting/reportlab.png&quot; alt=&quot;ReportLab&quot; width=&quot;300&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;reportlab-plus&quot;&gt;ReportLab PLUS&lt;/h3&gt;
&lt;p&gt;In comparison to the open-source version of ReportLab, the most prominent features of Reportlab PLUS are&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a templating language&lt;/li&gt;
  &lt;li&gt;the ability to include vector graphics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The templating language is called RML (Report Markup Language), an XML dialect. Here is a sample of how it looks like, taken directly from the official documentation:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE document SYSTEM &quot;rml.dtd&quot;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;document&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;filename=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;example.pdf&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;pageTemplate&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;frame&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;first&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x1=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;72&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;y1=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;72&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;width=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;451&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;height=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;698&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/pageTemplate&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;stylesheet&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/stylesheet&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;story&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;para&amp;gt;&lt;/span&gt;
            This is the &quot;story&quot;. This is the part of the RML document where
            your text is placed.
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/para&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;para&amp;gt;&lt;/span&gt;
            It should be enclosed in &quot;para&quot; and &quot;/para&quot; tags to turn it into
            paragraphs.
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/para&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/story&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/document&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The idea here is that you can have any program produce such an RML document, not just Python, which can then be transformed into a PDF document by ReportLab PLUS.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Python offers various libraries to create professional reports and factsheets. If you are a good at HTML + CSS have a look at Plotly Dash or Panel or write your HTML documents directly with the help of the &lt;code class=&quot;highlighter-rouge&quot;&gt;to_html&lt;/code&gt; method in Pandas.&lt;/p&gt;

&lt;p&gt;If you need your report as Excel file (or if you hate CSS), Pandas + XlsxWriter/OpenPyXL or xlwings might be the right choice - you can still export your Excel document as PDF file. xlwings is the better choice if you want to  split the design and code work. XlsxWriter/OpenPyxl is the better choice if it needs to be scalable and run on a server.&lt;/p&gt;

&lt;p&gt;If you need to generate PDF files at high speed, check out ReportLab. It has a steep learning curve and requires to write quite some code but once the code has been written, it works at high speed.&lt;/p&gt;</content><author><name>Felix Zumstein</name></author><category term="xlwings" /><category term="reporting" /><category term="pandas" /><category term="dash" /><category term="reportlab" /><category term="factsheet" /><category term="excel" /><category term="python" /><summary type="html">Python is a popular tool for all kind of automation needs and therefore a great candidate for your reporting tasks. There is a wealth of techniques and libraries available and we’re going to introduce five popular options here. After reading this blog post, you should be able to pick the right library for your next reporting project according to your needs and skill set.</summary></entry><entry><title type="html">Dynamic arrays in Excel with Python and xlwings</title><link href="https://www.xlwings.org/blog/dynamic-arrays-in-excel" rel="alternate" type="text/html" title="Dynamic arrays in Excel with Python and xlwings" /><published>2019-07-05T00:00:00+00:00</published><updated>2019-07-05T00:00:00+00:00</updated><id>https://www.xlwings.org/blog/dynamic-arrays-in-excel</id><content type="html" xml:base="https://www.xlwings.org/blog/dynamic-arrays-in-excel">&lt;p&gt;The days of &lt;code class=&quot;highlighter-rouge&quot;&gt;Ctrl-Shift-Enter&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;You can&apos;t change part of an array&lt;/code&gt; are finally over! Say hello to the new dynamic arrays that Microsoft has now (July 2019) rolled out to a broader user base: their Office 365 subscribers who are on the monthly channel.&lt;/p&gt;

&lt;p&gt;To check if you are on the monthly channel, go to &lt;code class=&quot;highlighter-rouge&quot;&gt;File&lt;/code&gt; &amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;Account&lt;/code&gt; and see if it mentions &lt;code class=&quot;highlighter-rouge&quot;&gt;Monthly Channel (Targeted)&lt;/code&gt; under &lt;code class=&quot;highlighter-rouge&quot;&gt;About Excel&lt;/code&gt;. If you are not, then you’ll need to wait a bit longer (or switch to the monthly channel). Also note that dynamic arrays are not yet part of Office 2019, so you will absolutely need an Office 365 subscription.&lt;/p&gt;

&lt;p&gt;To quickly double check if the respective update has already been installed, try if you have access to the &lt;code class=&quot;highlighter-rouge&quot;&gt;=UNIQUE&lt;/code&gt; formula. If you do, you’re all set for Excel’s most revolutionary feature since the introduction of the ribbon and the xlsx format. If you don’t, go to &lt;code class=&quot;highlighter-rouge&quot;&gt;File&lt;/code&gt; &amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;Account&lt;/code&gt; &amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;Office Updates&lt;/code&gt; and click on &lt;code class=&quot;highlighter-rouge&quot;&gt;Update Now&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Dynamic arrays are obviously great news if you work with Excel in general, but they also make writing UDFs (User Defined Functions) in Python via &lt;a href=&quot;https://www.xlwings.org&quot;&gt;xlwings&lt;/a&gt; a lot more powerful. This post assumes you know how xlwings UDFs work, so head over to the &lt;a href=&quot;http://docs.xlwings.org/en/stable/quickstart.html&quot;&gt;xlwings quickstart&lt;/a&gt; if you need a refresher.&lt;/p&gt;

&lt;h2 class=&quot;no_toc&quot; id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#whats-wrong-with-the-legacy-ctrl-shift-enter-array-formulas&quot; id=&quot;markdown-toc-whats-wrong-with-the-legacy-ctrl-shift-enter-array-formulas&quot;&gt;What’s wrong with the legacy Ctrl-Shift-Enter array formulas?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-new-dynamic-array-formulas-and-spill-errors&quot; id=&quot;markdown-toc-the-new-dynamic-array-formulas-and-spill-errors&quot;&gt;The new dynamic array formulas and &lt;code class=&quot;highlighter-rouge&quot;&gt;#Spill&lt;/code&gt; errors&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#xlwings-udfs-and-the-new-dynamic-arrays&quot; id=&quot;markdown-toc-xlwings-udfs-and-the-new-dynamic-arrays&quot;&gt;xlwings UDFs and the new dynamic arrays&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#new-built-in-excel-formulas-unique-etc&quot; id=&quot;markdown-toc-new-built-in-excel-formulas-unique-etc&quot;&gt;New built-in Excel formulas: UNIQUE etc.&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Prefer to watch a video? Here it is:&lt;/p&gt;

&lt;div class=&quot;embed-responsive embed-responsive-16by9 col-lg-6&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Gk0p3hFRo0k&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&quot;whats-wrong-with-the-legacy-ctrl-shift-enter-array-formulas&quot;&gt;What’s wrong with the legacy Ctrl-Shift-Enter array formulas?&lt;/h2&gt;

&lt;p&gt;Consider the following xlwings UDF:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;xlwings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dynamic_array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In older versions of Excel, if you write &lt;code class=&quot;highlighter-rouge&quot;&gt;=dynamic_array(4, 3)&lt;/code&gt; into &lt;code class=&quot;highlighter-rouge&quot;&gt;A1&lt;/code&gt;, then you would just get one value back instead of the full 4 x 3 array. To solve that, you’d have to use legacy array formulas: Select all cells for the result array (i.e. &lt;code class=&quot;highlighter-rouge&quot;&gt;A1:C4&lt;/code&gt; in the example), then, while the cells are selected, write the formula into the top left cell and finally hit &lt;code class=&quot;highlighter-rouge&quot;&gt;Ctrl-Shift-Enter&lt;/code&gt; (see the &lt;a href=&quot;http://docs.xlwings.org/en/stable/udfs.html#array-formulas-get-efficient&quot;&gt;xlwings docs&lt;/a&gt; for a step-by-step tutorial).&lt;/p&gt;

&lt;p&gt;The biggest issue arises when you want to change the formula to &lt;code class=&quot;highlighter-rouge&quot;&gt;=dynamic_array(5, 5)&lt;/code&gt;: You have to first delete the entire array (you need to select the entire array to be able to do that), then select the new 5 x 5 cell array, type in the new formula and finally repeat the &lt;code class=&quot;highlighter-rouge&quot;&gt;Ctrl-Shift-Enter&lt;/code&gt; madness.&lt;/p&gt;

&lt;p&gt;Things get even more complicated when you don’t know the dimensions of the array up front: You might, for example, query an API or database to download time series data of unknown length and return this via your UDF.&lt;/p&gt;

&lt;p&gt;xlwings offers a “hacky” solution for this issue: When you are on an older versions of Excel without dynamic arrays, you can use the &lt;code class=&quot;highlighter-rouge&quot;&gt;@xw.ret(expand=&apos;table&apos;)&lt;/code&gt; decorator which will automatically resize the legacy array for you:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;table&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dynamic_array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Still, this is hacky, slow and error prone so being able to let Excel take care of this properly is a game changer.&lt;/p&gt;

&lt;h2 id=&quot;the-new-dynamic-array-formulas-and-spill-errors&quot;&gt;The new dynamic array formulas and &lt;code class=&quot;highlighter-rouge&quot;&gt;#Spill&lt;/code&gt; errors&lt;/h2&gt;

&lt;p&gt;Whenever you use an Excel formula or xlwings UDF that returns an array, Excel will now automatically write out the whole array and put a blue line around it. No more difference to how you would type in a single cell formula and no more &lt;code class=&quot;highlighter-rouge&quot;&gt;Ctrl-Shift-Enter&lt;/code&gt;! The curly braces around your formula that appear with legacy arrays are also gone.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/dynamicarrays/dynamicarray.png&quot; alt=&quot;Dynamic Arrays&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If the array would overwrite existing cell values, it returns the new &lt;code class=&quot;highlighter-rouge&quot;&gt;#Spill&lt;/code&gt; error instead, allowing you to make room for the array. Once you move or delete the offending cell, Excel returns the array automatically and gets rid of the &lt;code class=&quot;highlighter-rouge&quot;&gt;#Spill&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/dynamicarrays/spillerror.png&quot; alt=&quot;Spill Error&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you ever want to prevent a formula from behaving like a new dynamic array, you simply have to start the formula with an &lt;code class=&quot;highlighter-rouge&quot;&gt;@&lt;/code&gt;, e.g.: &lt;code class=&quot;highlighter-rouge&quot;&gt;=@dynamic_array(3,4)&lt;/code&gt; and you’re back to the old behavior.&lt;/p&gt;

&lt;h2 id=&quot;xlwings-udfs-and-the-new-dynamic-arrays&quot;&gt;xlwings UDFs and the new dynamic arrays&lt;/h2&gt;

&lt;p&gt;The best part about xlwings and dynamic arrays is that they work out of the box without any changes. However, if you were using the &lt;code class=&quot;highlighter-rouge&quot;&gt;xw.ret(expand=&apos;table&apos;)&lt;/code&gt; decorator previously, make sure to remove it again as soon as you have upgraded to an Excel version that supports dynamic arrays!&lt;/p&gt;

&lt;p&gt;For example, building a UDF for &lt;a href=&quot;https://www.quandl.com/&quot; target=&quot;_blank&quot;&gt;Quandl&lt;/a&gt; time series is now as easy as:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;xlwings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;quandl&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# quandl.ApiConfig.api_key = &apos;MY_QUANDL_API_KEY&apos;
&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_quandl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quandl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And getting the Big Mac Index for the U.S. looks like that:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/dynamicarrays/bigmacindex.png&quot; alt=&quot;Big mac Index&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;new-built-in-excel-formulas-unique-etc&quot;&gt;New built-in Excel formulas: UNIQUE etc.&lt;/h2&gt;

&lt;p&gt;Dynamic arrays come with a few new built-in Excel functions that are worth mentioning. For example &lt;code class=&quot;highlighter-rouge&quot;&gt;=UNIQUE(A1:A12)&lt;/code&gt; returns a list of unique values:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/dynamicarrays/unique.png&quot; alt=&quot;Unique formula&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Other new dynamic array formulas worth checking out are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.office.com/en-us/article/filter-function-f4f7cb66-82eb-4767-8f7c-4877ad80c759&quot; target=&quot;_blank&quot;&gt;FILTER&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.office.com/en-us/article/randarray-function-21261e55-3bec-4885-86a6-8b0a47fd4d33&quot; target=&quot;_blank&quot;&gt;RANDARRAY&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.office.com/en-us/article/sequence-function-57467a98-57e0-4817-9f14-2eb78519ca90&quot; target=&quot;_blank&quot;&gt;SEQUENCE&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.office.com/en-us/article/sort-function-22f63bd0-ccc8-492f-953d-c20e8e44b86c&quot; target=&quot;_blank&quot;&gt;SORT&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.office.com/en-us/article/sortby-function-cd2d7a62-1b93-435c-b561-d6a35134f28f&quot; target=&quot;_blank&quot;&gt;SORTBY&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Felix Zumstein</name></author><category term="excel" /><category term="python" /><category term="xlwings" /><category term="dynamic arrays" /><category term="office365" /><summary type="html">The days of Ctrl-Shift-Enter and You can&apos;t change part of an array are finally over! Say hello to the new dynamic arrays that Microsoft has now (July 2019) rolled out to a broader user base: their Office 365 subscribers who are on the monthly channel.</summary></entry><entry><title type="html">Tired of Excel’s VLOOKUP? Use SQL queries on Excel ranges!</title><link href="https://www.xlwings.org/blog/in-excel-sql" rel="alternate" type="text/html" title="Tired of Excel’s VLOOKUP? Use SQL queries on Excel ranges!" /><published>2017-07-15T00:00:00+00:00</published><updated>2017-07-15T00:00:00+00:00</updated><id>https://www.xlwings.org/blog/in-excel-sql</id><content type="html" xml:base="https://www.xlwings.org/blog/in-excel-sql">&lt;p&gt;If you have the free &lt;a href=&quot;/&quot;&gt;xlwings&lt;/a&gt; Excel add-in installed, you can query your data using standard
SQL statements, like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/sql/sql.gif&quot; alt=&quot;sql in Excel&quot; /&gt;&lt;/p&gt;

&lt;h2 class=&quot;no_toc&quot; id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#what-is-xlwings&quot; id=&quot;markdown-toc-what-is-xlwings&quot;&gt;What is xlwings?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-sql-formula&quot; id=&quot;markdown-toc-the-sql-formula&quot;&gt;The sql formula&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#behind-the-scenes&quot; id=&quot;markdown-toc-behind-the-scenes&quot;&gt;Behind the scenes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;what-is-xlwings&quot;&gt;What is xlwings?&lt;/h2&gt;

&lt;p&gt;xlwings is a &lt;a href=&quot;https://www.python.org/&quot; target=&quot;_blank&quot;&gt;Python&lt;/a&gt; package that allows us to write Python functions instead of VBA code. It recently added a full
blown Excel add-in that brought us “xlwings extensions”: 
These are Excel functions that are available as soon as we install the add-in without any more setup steps required. The first built-in
extension is the “In-Excel SQL” extension that brings the power of SQL to your Excel spreadsheets.
Typically, you would use the &lt;code class=&quot;highlighter-rouge&quot;&gt;VLOOKUP&lt;/code&gt; formula to connect data from two distinct regions in Excel. Or maybe
you would prefer &lt;code class=&quot;highlighter-rouge&quot;&gt;INDEX/MATCH&lt;/code&gt; over &lt;code class=&quot;highlighter-rouge&quot;&gt;VLOOKUP&lt;/code&gt; if you are an advanced user. However, the functionality of these formulas is limited and if you have
a lot of these functions, Excel can become quite slow. Often, we were banging our heads against the wall saying “this would be so
easy in SQL!”, well…now it is.&lt;/p&gt;

&lt;h2 id=&quot;the-sql-formula&quot;&gt;The sql formula&lt;/h2&gt;

&lt;p&gt;The usage of the sql formula is simple:&lt;/p&gt;

&lt;div class=&quot;language-vb highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SQL Statement&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table_a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You simply have to refer to your tables as &lt;code class=&quot;highlighter-rouge&quot;&gt;a&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;b&lt;/code&gt; etc. in your SQL statement in the order you pass them in.&lt;/p&gt;

&lt;p&gt;For more complex queries, it is advisable to write the query into a cell instead of directly in the formula which also allows
you to use multiple lines in your query (by using &lt;code class=&quot;highlighter-rouge&quot;&gt;Alt-Enter&lt;/code&gt; for a new line), e.g.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/sql/sql_multiline.png&quot; alt=&quot;sql multiline&quot; width=&quot;335&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;behind-the-scenes&quot;&gt;Behind the scenes&lt;/h2&gt;

&lt;p&gt;xlwings uses an in-memory &lt;a href=&quot;https://www.sqlite.org/&quot; target=&quot;_blank&quot;&gt;sqlite&lt;/a&gt; database to provide the functionality, so refer to their homepage
for further information regarding the sqlite dialect of SQL.&lt;/p&gt;</content><author><name>Felix Zumstein</name></author><category term="excel" /><category term="python" /><category term="xlwings" /><category term="unittest" /><summary type="html">If you have the free xlwings Excel add-in installed, you can query your data using standard SQL statements, like this:</summary></entry><entry><title type="html">Unit Tests for Microsoft Excel</title><link href="https://www.xlwings.org/blog/unittests-for-microsoft-excel" rel="alternate" type="text/html" title="Unit Tests for Microsoft Excel" /><published>2016-10-17T00:00:00+00:00</published><updated>2016-10-17T00:00:00+00:00</updated><id>https://www.xlwings.org/blog/unittests-for-microsoft-excel</id><content type="html" xml:base="https://www.xlwings.org/blog/unittests-for-microsoft-excel">&lt;p&gt;Many (business) users use Excel to create full blown applications. However, unlike applications developed by professional
software developers, Excel tools fall short of most software development practices that are considered minimal standards.
This introduces risks that can lead to reputational damages and substantial financial losses (remember the
&lt;a href=&quot;http://www.forbes.com/sites/tomgroenfeldt/2013/02/19/solutions-to-spreadsheet-risk-post-jpms-london-whale&quot; target=&quot;_blank&quot;&gt;«London Whale»&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This blog post introduces the concept of test-driven development before it shows you how to write an automated unit test
for a VBA function. All we need is &lt;a href=&quot;https://www.python.org&quot; target=&quot;_blank&quot;&gt;Python&lt;/a&gt;, a free and open-source programming
language and &lt;a href=&quot;/&quot;&gt;xlwings&lt;/a&gt;, our Python package that connects to Excel. The concepts shown work on Windows and Mac.&lt;/p&gt;

&lt;p&gt;If you already know what I am speaking of and don’t want to go through all the step by step instructions below: Simply head over to
&lt;a href=&quot;https://github.com/xlwings/xlwings-automated-testing&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt; where you
can clone or download a fully working example. For a broader but less technical view of this topic, download our &lt;a href=&quot;/whitepapers&quot;&gt;white paper&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class=&quot;no_toc&quot; id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#the-directory-of-horror-john_handover&quot; id=&quot;markdown-toc-the-directory-of-horror-john_handover&quot;&gt;The directory of horror: «John_handover»&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#test-driven-development&quot; id=&quot;markdown-toc-test-driven-development&quot;&gt;Test-driven development&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-missing-piece-python-with-xlwings&quot; id=&quot;markdown-toc-the-missing-piece-python-with-xlwings&quot;&gt;The missing piece: Python with xlwings&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#before-we-can-start-install-python&quot; id=&quot;markdown-toc-before-we-can-start-install-python&quot;&gt;Before we can start: Install Python&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#a-simple-unit-test&quot; id=&quot;markdown-toc-a-simple-unit-test&quot;&gt;A simple unit test&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#no-guarantee-for-success&quot; id=&quot;markdown-toc-no-guarantee-for-success&quot;&gt;No guarantee for success&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#where-to-go-from-here&quot; id=&quot;markdown-toc-where-to-go-from-here&quot;&gt;Where to go from here&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;the-directory-of-horror-john_handover&quot;&gt;The directory of horror: «John_handover»&lt;/h2&gt;
&lt;p&gt;We’ve all been there: On day one of our new job we are assigned the responsibility over a folder called «John_handover». John left
the company a while ago, but not without leaving behind a spreadsheet tool of about 20 MB of size, containing VBA and complex formulas
that involve multiple sheets (some of them hidden) and countless VLOOKUPs. Your boss is kind enough to tell you the secret of how to
manage it: «Just don’t touch anything and you will be fine.»&lt;/p&gt;

&lt;p&gt;After getting over your first shock and thinking about how to get out of the mess, you come up with the solution of having IT transform
this Excel tool into a proper application. But it turns out that the IT budget has already been spent for this year and for next year
there are already a few other projects queuing up.&lt;/p&gt;

&lt;p&gt;Bottom line is that you’re left with a tool that causes you a headache and that you don’t really understand. You would like to simplify
it but are afraid of breaking something.&lt;/p&gt;

&lt;p&gt;Guess what: In software development, this situation is the rule rather than the exception. But there’s ways to get confidence over such
legacy tools and make sure that they still work as they are supposed to after a change. Say hello to test-driven development.&lt;/p&gt;

&lt;h2 id=&quot;test-driven-development&quot;&gt;Test-driven development&lt;/h2&gt;
&lt;p&gt;A core component of modern software development is writing automated tests. Most commonly, these tests make sure that a small independent
unit of the program’s overall logic works correctly given a few test cases that are therefore called «unit tests». The paradigm that wants
you to write the tests first and only then adopt the logic of the program until all tests pass, is called «test driven development» (TDD).
In a nutshell, the thinking behind TDD is: «If it’s not tested, it’s broken» (Bruce Eckel in his book «Thinking in Java», 3rd edition).&lt;/p&gt;

&lt;p&gt;Unit tests are a very effective way to automatically test a large number of use cases (including edge cases) to make sure that a program
still works correctly under all tested circumstances even after adding or changing pieces of the program.&lt;/p&gt;

&lt;p&gt;Practically all common programming languages come with either a built- in framework for writing unit tests or integrate easily with an
existing framework. Excel, however, doesn’t offer any means to effectively write unit tests. That is, until now.&lt;/p&gt;

&lt;h2 id=&quot;the-missing-piece-python-with-xlwings&quot;&gt;The missing piece: Python with xlwings&lt;/h2&gt;
&lt;p&gt;Python is a widely used programming language that excels at interacting with all kinds of different systems which is why it is sometimes
referred to as a «glue language». To interact smoothly between Excel and Python, we created xlwings, a Python package that is free and
open-source, like Python itself.&lt;/p&gt;

&lt;p&gt;xlwings allows you to automate and program Excel with Python and therefore gives you access to Python’s built-in unittest module. Writing
unit tests for Excel with Python and xlwings is simple and intuitive:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;No Excel add-ins required&lt;/li&gt;
  &lt;li&gt;No changes to the original Excel file necessary&lt;/li&gt;
  &lt;li&gt;Create tests covering spreadsheet logic or VBA code&lt;/li&gt;
  &lt;li&gt;You only need a Python installation with xlwings&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;before-we-can-start-install-python&quot;&gt;Before we can start: Install Python&lt;/h2&gt;

&lt;p&gt;Simply download &lt;a href=&quot;https://www.anaconda.com/distribution/&quot; target=&quot;_blank&quot;&gt;Anaconda&lt;/a&gt;, a free scientific Python distribution that 
comes in the form of a one-click installer that already contains everything we need. After downloading, install Anaconda with the 
defaults.&lt;/p&gt;

&lt;p&gt;If it worked out correctly, you should see something like the following when typing &lt;code class=&quot;highlighter-rouge&quot;&gt;python&lt;/code&gt; at a command prompt (on Windows) or 
a Terminal (on Mac):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;C:&lt;span class=&quot;se&quot;&gt;\U&lt;/span&gt;sers&lt;span class=&quot;se&quot;&gt;\f&lt;/span&gt;elix&amp;gt; python
Python 3.5.2 |Anaconda 4.2.0 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;64-bit&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;| &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;default, Jul  5 2016, 11:41:13&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;MSC v.1900 64 bit &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AMD64&lt;span class=&quot;o&quot;&gt;)]&lt;/span&gt; on win32
Type &lt;span class=&quot;s2&quot;&gt;&quot;help&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;copyright&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;credits&quot;&lt;/span&gt; or &lt;span class=&quot;s2&quot;&gt;&quot;license&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;more information.
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;a-simple-unit-test&quot;&gt;A simple unit test&lt;/h2&gt;

&lt;p&gt;Create a new macro-enabled workbook with the name &lt;code class=&quot;highlighter-rouge&quot;&gt;mybook.xlsm&lt;/code&gt; and save it in the folder where you started the command prompt: 
in my example that would be &lt;code class=&quot;highlighter-rouge&quot;&gt;C:\Users\Felix&lt;/code&gt;. Open the VBA editor by clicking &lt;code class=&quot;highlighter-rouge&quot;&gt;Alt-F11&lt;/code&gt;, Insert a new VBA module (&lt;code class=&quot;highlighter-rouge&quot;&gt;Insert &amp;gt; Module&lt;/code&gt;) and paste the following
simple VBA function:&lt;/p&gt;

&lt;div class=&quot;language-vb highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;Function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mysum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Double&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mysum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;End&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To access this function in Python, you can easily map it with xlwings: In the Python session we started above, execute the
following lines to import the xlwings package, then map the workbook and finally the VBA function. If you have saved your workbook
somewhere else than in the current working directory of the command prompt you would need to provide its full path.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;xlwings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;mybook.xlsm&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mysum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;macro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;mysum&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now you can use &lt;code class=&quot;highlighter-rouge&quot;&gt;mysum&lt;/code&gt; as if it was a native Python function but it’s actually running the VBA code:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mysum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s now create a Python file called &lt;code class=&quot;highlighter-rouge&quot;&gt;test_mybook.py&lt;/code&gt;. In your favorite text editor, copy/paste the following into that
file:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;unittest&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;xlwings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TestMyBook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unittest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TestCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# setUp will be called before the execution of each unit test
&lt;/span&gt;        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;mybook.xlsm&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# map workbook
&lt;/span&gt;        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mysum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;macro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Module1.mysum&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# map function
&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_mysum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mysum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# get result from VBA
&lt;/span&gt;        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;assertAlmostEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# test if result corresponds to the expected value
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;__main__&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# This allows us to easily run the tests from the command prompt
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;unittest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This file contains quite a bit of boilerplate code but the actual unit test is defined in &lt;code class=&quot;highlighter-rouge&quot;&gt;test_mysum&lt;/code&gt; where we assert
that the result as delivered by the VBA function corresponds to the expected result. We use &lt;code class=&quot;highlighter-rouge&quot;&gt;assertAlmostEqual&lt;/code&gt; so that 
the test doesn’t fail because of floating point issues.&lt;/p&gt;

&lt;p&gt;To run all unit tests in that file (ok, it’s just one for now), simply call the following from a command prompt
(if you still have a Python session running, then make sure to quit first by hitting &lt;code class=&quot;highlighter-rouge&quot;&gt;Ctrl-D&lt;/code&gt; or entering &lt;code class=&quot;highlighter-rouge&quot;&gt;quit()&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;C:&lt;span class=&quot;se&quot;&gt;\U&lt;/span&gt;sers&lt;span class=&quot;se&quot;&gt;\f&lt;/span&gt;elix&amp;gt; python test_mybook.py 
&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;----------------------------------------------------------------------&lt;/span&gt;
Ran 1 &lt;span class=&quot;nb&quot;&gt;test &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;0.083s

OK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The report gives you a dot for all tests that ran successfully. If we would introduce an error in the VBA code by changing it
like this:&lt;/p&gt;

&lt;div class=&quot;language-vb highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;Function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mysum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Double&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mysum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;End&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then the output would accordingly tell us about the failed test like this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;C:&lt;span class=&quot;se&quot;&gt;\U&lt;/span&gt;sers&lt;span class=&quot;se&quot;&gt;\f&lt;/span&gt;elix&amp;gt; python test_mybook.py 
F
&lt;span class=&quot;o&quot;&gt;======================================================================&lt;/span&gt;
FAIL: test_mysum &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;__main__.TestMyBook&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;----------------------------------------------------------------------&lt;/span&gt;
Traceback &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;most recent call last&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:
  File &lt;span class=&quot;s2&quot;&gt;&quot;test_mybook.py&quot;&lt;/span&gt;, line 17, &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;test_mysum
    self.assertAlmostEqual&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;3, result&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
AssertionError: 3 &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; 2.0 within 7 places

&lt;span class=&quot;nt&quot;&gt;----------------------------------------------------------------------&lt;/span&gt;
Ran 1 &lt;span class=&quot;nb&quot;&gt;test &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;0.093s

FAILED &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;failures&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;no-guarantee-for-success&quot;&gt;No guarantee for success&lt;/h2&gt;

&lt;p&gt;Unit tests are tremendously helpful in regaining trust over a complex workbook and feel comfortable before 
doing changes. However, even if all tests pass, it’s in no way a guarantee for bug free code.&lt;/p&gt;

&lt;p&gt;First, only situations that you can think of are tested and second, the tests might pass if you pick the wrong edge cases.
In the sample above, if I would have tested &lt;code class=&quot;highlighter-rouge&quot;&gt;2 + 2 = 4&lt;/code&gt;, instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;1 + 2 = 3&lt;/code&gt;, then the unit test would have
still passed when using the multiplication by mistake. Hence it usually makes sense to test a few scenarios
including various edge cases.&lt;/p&gt;

&lt;h2 id=&quot;where-to-go-from-here&quot;&gt;Where to go from here&lt;/h2&gt;

&lt;p&gt;The unit test that we’ve built in this blog post is just a simple example of writing a test that checks the logic
of a VBA function.&lt;/p&gt;

&lt;p&gt;However, you can use the same unit testing infrastructure to validate a model by comparing the output of a VBA function 
with the output of an alternative implementation, e.g. in one of Python’s heavily tested 3rd party packages.&lt;/p&gt;

&lt;p&gt;Also, you can write tests that control the correct formulas and relationships in cells as this is where you often introduce
errors by copy/pasting or by dragging the wrong cells and consequently filling cells with the wrong formula.&lt;/p&gt;

&lt;p&gt;To get an overview over these techniques, too, download our &lt;a href=&quot;/whitepapers&quot;&gt;white paper&lt;/a&gt;.&lt;/p&gt;</content><author><name>Felix Zumstein</name></author><category term="excel" /><category term="python" /><category term="xlwings" /><category term="unittest" /><summary type="html">Many (business) users use Excel to create full blown applications. However, unlike applications developed by professional software developers, Excel tools fall short of most software development practices that are considered minimal standards. This introduces risks that can lead to reputational damages and substantial financial losses (remember the «London Whale»).</summary></entry><entry><title type="html">How to fetch Market Data in Excel like a Pro</title><link href="https://www.xlwings.org/blog/how-to-fetch-market-data-in-excel-like-a-pro" rel="alternate" type="text/html" title="How to fetch Market Data in Excel like a Pro" /><published>2016-09-23T00:00:00+00:00</published><updated>2016-09-23T00:00:00+00:00</updated><id>https://www.xlwings.org/blog/how-to-fetch-market-data-in-excel-like-a-pro</id><content type="html" xml:base="https://www.xlwings.org/blog/how-to-fetch-market-data-in-excel-like-a-pro">&lt;p&gt;Get market data from Quandl and other data providers directly via cell formulas:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/fetch-market-data/QDH.gif&quot; alt=&quot;excel cell formula&quot; class=&quot;img-fluid&quot; /&gt;&lt;/p&gt;

&lt;h2 class=&quot;no_toc&quot; id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#bloombergs-infamous-bdh-formula&quot; id=&quot;markdown-toc-bloombergs-infamous-bdh-formula&quot;&gt;Bloomberg’s infamous BDH formula&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#enter-xlwings&quot; id=&quot;markdown-toc-enter-xlwings&quot;&gt;Enter xlwings&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#building-qdh&quot; id=&quot;markdown-toc-building-qdh&quot;&gt;Building QDH&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;bloombergs-infamous-bdh-formula&quot;&gt;Bloomberg’s infamous BDH formula&lt;/h2&gt;

&lt;p&gt;Tell a financial analyst to download market data into Excel, and the odds are that they will start
typing the following formula in an Excel spreadsheet: &lt;code class=&quot;highlighter-rouge&quot;&gt;=BDH(&quot;TICKER&quot;, &quot;PX_LAST&quot;,...)&lt;/code&gt;.
This formula stands for “Bloomberg Data History” and will download the end-of-period prices of the indicated ticker
directly into your spreadsheet - given that you have Bloomberg’s Excel addin connected to a Bloomberg terminal.&lt;/p&gt;

&lt;p&gt;Now, the special thing about that formula is that it fills your sheet with an array of historical prices from a single
cell - without being a proper Excel array formula! While this is slightly against the idea of how Excel is supposed to work, it’s
very useful in practice and usually one of the first things you start missing when you change jobs and loose access to a
Bloomberg terminal.&lt;/p&gt;

&lt;h2 id=&quot;enter-xlwings&quot;&gt;Enter xlwings&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/&quot;&gt;xlwings&lt;/a&gt; is a free and open-source Python package that allows you to ditch VBA in favor of
Python and therefore allows you to achieve quite a few nice things quite easily.
One of these nice things that we’ve added with the recent release of v0.10 is the possibility to create BDH-style
formulas for any available data source and in exactly the way you like!&lt;/p&gt;

&lt;h2 id=&quot;building-qdh&quot;&gt;Building QDH&lt;/h2&gt;

&lt;p&gt;While it’s equally easy to use Yahoo! Finance or Google Finance data (e.g. by using the
&lt;a href=&quot;https://github.com/pydata/pandas-datareader&quot; target=&quot;_blank&quot;&gt;pandas-datareader&lt;/a&gt; package), for this sample, we’re going to use 
&lt;a href=&quot;https://www.quandl.com&quot; target=&quot;_blank&quot;&gt;Quandl&lt;/a&gt;, a data platform for both free and paid data. Although Quandl offers an excellent Excel addin, it’s
missing (you guessed it) a BDH-like formula. Let’s build it and call it “QDH”.&lt;/p&gt;

&lt;h3 id=&quot;step-1-install-python-with-xlwings&quot;&gt;Step 1: Install Python with xlwings&lt;/h3&gt;

&lt;p&gt;While there are many ways to install Python and the required dependencies, we strongly recommend you to download and
install &lt;a href=&quot;https://www.anaconda.com/distribution/&quot; target=&quot;_blank&quot;&gt;Anaconda&lt;/a&gt; which already comes with everything we need in a single installer.&lt;/p&gt;

&lt;p&gt;Just run &lt;code class=&quot;highlighter-rouge&quot;&gt;conda upgrade xlwings&lt;/code&gt; from a command prompt to make sure you have at least version. Please follow
the xlwings &lt;a href=&quot;http://docs.xlwings.org/en/stable/udfs.html&quot; target=&quot;_blank&quot;&gt;docs&lt;/a&gt; to install the xlwings addin.&lt;/p&gt;

&lt;h3 id=&quot;step-2-create-the-qdh-formula&quot;&gt;Step 2: Create the QDH formula&lt;/h3&gt;

&lt;p&gt;Let’s start a new xlwings project by executing the following from the command prompt:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;xlwings quickstart qdh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will create a &lt;code class=&quot;highlighter-rouge&quot;&gt;qdh&lt;/code&gt; directory with an Excel file &lt;code class=&quot;highlighter-rouge&quot;&gt;qdh.xlsm&lt;/code&gt; and an empty Python source file &lt;code class=&quot;highlighter-rouge&quot;&gt;qdh.py&lt;/code&gt;. In the Python source
file we’re now going to write the following function:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;xlwings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;quandl&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;quandl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApiConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;MY_QUANDL_API_KEY&apos;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;table&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# this makes it a dynamic array
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;QDH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quandl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: You don’t need to provide a Quandl API key (comes with a free account), but the number of calls will be limited,
see &lt;a href=&quot;https://help.quandl.com/article/68-is-there-a-rate-limit-or-speed-limit-for-api-usage&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That’s all we need for the most basic version of QDH! The magic happens in the return decorator where we tell the formula 
to autoexpand the data into a dynamic array.&lt;/p&gt;

&lt;p&gt;Switch over to Excel, import the function via the xlwings addin, type &lt;code class=&quot;highlighter-rouge&quot;&gt;=BDH(&quot;WIKI/AAPL&quot;, &quot;2016-01-01&quot;)&lt;/code&gt; into a cell and
hit enter. You will be presented with the end-of-day information for Apple since the beginning of 2016 (this corresponds
to the animated gif at the top of this article.)&lt;/p&gt;

&lt;p&gt;We can make this formula a bit more useful and robust by changing it like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;xlwings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;quandl&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;datetime&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;quandl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApiConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;MY_QUANDL_API_KEY&apos;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;table&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;QDH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;isinstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strftime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;%Y-%m-%d&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;isinstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strftime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;%Y-%m-%d&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quandl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;{0}.{1}&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                      &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can link to date-formatted cells and also specify which column we would like to fetch. Following the setup of the animated GIF, 
type the following into cell A4: &lt;code class=&quot;highlighter-rouge&quot;&gt;=QDH(A3,11,$B$1,$D$1)&lt;/code&gt;. Here, &lt;code class=&quot;highlighter-rouge&quot;&gt;11&lt;/code&gt; is the 11th column from before, i.e. the Adj. Close:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/fetch-market-data/QDH2.gif&quot; alt=&quot;bloomberg-like excel udf&quot; /&gt;&lt;/p&gt;</content><author><name>Felix Zumstein</name></author><category term="excel" /><category term="quandl" /><category term="xlwings" /><summary type="html">Get market data from Quandl and other data providers directly via cell formulas:</summary></entry></feed>