Making sense of challenging data topics one step at a time.

Author: Dominick Raimato Page 4 of 5

Mr. Bad Example – Bar and Column Charts Best Practices

I cannot begin to express how often I see bad examples of data visualization. Sometimes it is unintentional but it can have far reaching implications. Not following best practices and visualization standards often misleads report consumers and eventually bad decisions.

Even worse, report creators often manipulate these visuals to get the data to support their narrative. I am all for sharing a narrative with data but I draw the line at manipulation. If you have to twist and manipulate your visuals to tell your story, it is probably time to get a new story.

I am passionate about this topic because I watch people manipulated by these visuals all of the time. Sales presentations, all hands meetings at work, poll results, and even the news media commit these violations. My goal for this article is two fold. The first is to help you understand the why of these best practices for your own reports. The second is to help you identify these violations in the wild and help call them out.

My articles usually focus on Power BI. However, this article applies to any visualization tool you will encounter.

The Importance of Best Practices and Visualization Standards

You might be asking to your self “why is Dominick so passionate about this?” To me, the loss is considerable if you are not careful. Here are some common scenarios where not following best practices can get you into trouble.

Financial Costs

This is the first scenario that often comes to mind when it comes to bad visualizations. We use reports and visualizations to make data driven decisions.

Let’s pretend you own a store that sells widgets. You take a look at a chart that shows your sales doubling month over month. As a shop owner, you might double your order for next month because if the trend continues you want to be prepared. It totally makes sense! You double up your orders (or even triple them) to meet demand.

Column Chart Showing Revenue Doubling Each Month

However, I left something out of this chart. There was no scale and my axis did not start at zero. In fact, I never actually doubled my sales. I only had a 10% increase of sales month over month.

Column Chart Showing the Same Data Above with Proper Scale and Labels

Now you might be thinking that this really is not a big deal as widgets do not have an expiration date. But the implications of this could be considerable as you took cash in hand and put it into inventory. You still have an assets on hand with the widgets, but you will need to sell them to make sure you have cash on hand. This could put you in a bind as an owner as you could potentially become “cash poor” which results in trouble paying bills, making payroll, and even paying yourself.

Someone Could Get Fired

Have you ever created a report that could get someone fired? I have!

One of the most common reports I see are around service level agreements – also known as SLAs. SLAs determine effectiveness of a process or service. Contracts include these metrics that help determine success of services.

Remember when pizza shops had the “30 minutes or its free” guarantee? It went away before I was old enough to buy my own pizza. Likely because someone did an analysis of their metrics and found they were not delivering enough pizzas on time.

If you managed a pizza shop, you might have had an SLA of delivering pizzas in under 30 minutes 97% of the time. That means out of 100 orders, three of them could be free because they did not make it on time. Your district or regional manager likely has a report showing your performance. What if they showed a report that showed a trend where your on time delivery metric dropped significantly. You might be in danger of losing your job for not performing as expected.

But if the chart is only moving from 99% of the time to 98%, it is still above the SLA and overall not too bad. The point here is that visualizations not following best practices can imply someone is not performing well in their job. This leads to someone getting disciplined even if they are performing well in their job.

Your Future Ambitions

People are often looking for ways to grow their career. They put together statistics and figures to show how great they are and why they deserve a raise or promotion. They might also provide some of the same information to help those above them move up the ladder as well. But what do you think the implications are if your visualizations used for this purpose are found to not use best practices?

I see it as a breech of trust. While it may seem insignificant at the moment, the impact could be long term in your career. Every report created could be scrutinized and called into question. It seems crazy but the potential is there. If a decision made from the report costs your organization a considerable amount, you might even lose your job!

This is why I believe it is important to consider best practices whenever creating reports. Leaders must take the time to make sure their teams understand these practices and live them for the benefit of their team.

What Should I Know About Best Practices?

There are 4 best practices that you should be concerned with when you create your reports. Follow these and you will be well on your way to building quality reports.

Best Practices to Follow #1 – Always Start at Zero

This is a non-negotiable standard when it comes to bar and column charts. They must always start at zero to avoid misleading consumers. This is the most common infraction that I see when it comes to data visualizations.

As discussed above, I can show amplified trends by changing the axis to start at a higher value. This also causes confusion and frustration for report consumers.

Column Chart That Starts at Zero

Best Practices to Follow #2 – Label Your Axis

The next most common ignored best practice is labeling your axis. We see the categorical axis getting a label (category, time, etc.) but he value axis does not get the same love.

To avoid confusion, you will want to label your value axis to match the measures in your chart. There is some flexibility with this as you can change the units to make it easier to read. For example, you can set the units to millions to compress the chart and make it easier to read. While you might be distorting the view some, the labels will clearly define the scale and make it easier to understand.

Labeling Your Categorical Axis and Value Axis

I also recommend using some lines to highlight your axis across the chart. I worked with an organization a few years ago that hated those lines. It was forbidden to add them to any chart in a presentation. While I strongly disagreed with this approach, they used data labels which helped keep things in context when viewing a chart.

Best Practices to Follow #3 – Use Data Labels to Highlight Key Values

My friend and colleague Sally Paczosa always used the term “hover to discover”. I stole her line and say it all the time when it comes to reviewing reports in Power BI. If I want to know the actual number, I can hover over a column or bar and see what the value is. It is one of the best features that people forget about when it comes to reporting.

But as much as I love it, I always have to consider my audience for a report. If my consumers are fluent with tools like Power BI, they already know what to do. Without proper training, they might miss that opportunity and never actually do it. In the same vein, I have to consider the venue they will consume the report. They can “hover to discover” all day long at their computer. But what if they are in a town hall meeting looking at a chart being presented? How can they view the actuals?

Data labels allow us to quickly identify key measures on a report and allow us to add context quickly. This prevents the fatigue of trying to match columns to the axis and accelerates understanding.

Using Data Labels on a Column Chart

Best Practices to Follow #4 – Add Lines for Clarity

I like adding lines to help provide better context and clarity to a chart. This is extremely helpful for reports that will be consumed without a talk track behind them. You can highlight common statistical measures such as maximum, minimum, mean, or median. You can even add a constant line if you want to highlight a particular goal. If not used properly, it could clutter your charts. However, properly placed it could make a huge difference.

Adding a Constant Line to Highlight a Goal

Best Practices Apply to Chart Types as Well!

Which is better? A bar chart or column chart? The answer will depend on the context of your data and the intent of your visualization. Here are some thoughts to help you make the right selection

Column Chart vs Bar Chart

People tend to use these terms interchangeably but don’t realize there is a difference between a bar and column chart. In a bar chart, the data is represented horizontally. Column charts represent the data vertically.

Column Chart vs Bar Chart

While it may seem inconsequential to interchangeably use these terms, it is important to really understand the difference to help represent your data.

Column Charts

Column charts can be used for categorical data, but they often shine when it comes to series based data. The natural left to right progression of the canvas allows users to follow a timeline to see trends.

They also shine because the vertical orientation of the values is easier to grasp some statistical measures such as maximum and minimum. It is less cluttered in this orientation and the labels are easier to read.

Bar Charts

Bar charts do an amazing job of showing large amounts of data in a compressed area. If you have long list of category labels, they tend to fit better horizontally. This prevents crowding and confusion.

In addition, they do a great job of allowing users to scroll down for large amounts of data. Naturally, your venue is going to have an impact on this functionality. You cannot just scroll down on a chart if you are in the audience of a town hall meeting. But for reports that are consumable at your desk, it is easy to scroll down and see more information.

Stacked and 100% and Clustered, Oh My!

Now that you have a hint at which direction you want your charts, it is time to pick the right one. If you only have a single series of data, then you could pick a stacked or clustered chart and it would not make a difference. But depending on what you are trying to show, it is important to pick the right chart.

Stacked

Stacked charts show the composite makeup of the whole value. Let’s stay I own a store but recently started selling my products online. I might want to see the breakdown of my revenue from my store versus online. A stacked column chart allows me to quickly identify the makeup of revenue which can help provide context. For example, if I have a month with higher than expected revenue, I might see that online sales were higher than in the past.

Example of a Stacked Column Chart

While this chart style is versatile and useful, it is not always easy to make comparisons. The orientation of the values makes it difficult for one to one comparisons.

100% Stacked

Similar to the stacked chart, 100% stacked charts show data in a similar format. The biggest difference is that the bar or column do not show totals. Instead, it shows the percentage of the composite elements.

Take our store versus online sales question. If you looked at the chart above, you could see the breakdown. But what if I wanted to understand the percentage of my sales that were in my store versus online. I could do some math to figure it out, but it is easier to show in the 100% stacked chart.

Example of a 100% Stacked Bar Chart

This is where the 100% stacked chart shines. It allows us to understand distribution a little easier which gives us a better comparison. We can quickly identify trends of online sales and see if they are increasing, decreasing, or remaining flat over time. The only downside is that you cannot see the overall totals.

Clustered

Clustered charts allow for quick comparisons. Not as precise as the other two charts, it does a good job of allowing you to compare two values side by side. Where most people go wrong is they try to compare categorical data which can get confusing. I like to use it to compare year over year or even month over month data. This allows for a close comparison without a lot of clutter.

Example of a Clustered Column Chart

What If I Am Unsure of Which One to Pick?!

The best part of most tools is that you can quickly change which chart you are using! Try some different ones. Maybe you will need to use a couple of variations to get your data to appear correctly. The goal is to communicate a story. One of my favorite ideas is to show the visualization to someone and have them interpret it for me. Don’t give them any hints – let them tell you. If what they say matches your narrative, then you are likely on the right track!

Next Steps

Now that you have a better understanding of best practices when it comes to bar and column charts, what do you do next? My first recommendation is to go back through your reports and presentations and find out if you were following these best practices. If you weren’t, try to understand why. I find that we tend to take shortcuts to save time when we have a hard deadline coming up. Building in a little extra time in your plan helps ensure nothing gets left out.

What about when we see best practices not being followed? What do we do? I find most people make these mistakes out of ignorance. They might know all of the best practices or how to apply them. I always recommend coaching people and helping them learn. I also find being the best example helps change the behaviors around you. By getting others to follow your lead, you will find others will do the same.

How often do you see these best practices ignored? Do you have any egregious examples? If so, tell me in the comments below!

Conditional Formatting – Moving to the Next Level

Last week, I went through the basics of conditional formatting. I shared how it helps take your reports to the next level and the steps required to use it. We looked at gradient and rule based conditional formatting but did not touch the field option. I wanted to split this one out because it can be a little more complicated but is quite versatile.

What is Field Based Conditional Formatting?

Isn’t all conditional formatting based upon fields? Of course it is! We are always looking at field values to activate conditional formatting. Field based conditional formatting is not looking at the field values for what needs to happen. Instead, it is looking to the field for what formatting is supposed to appear.

Let’s use our example from last week where we looked at revenue versus goals. We used a simple set of rules to highlight table rows as red, yellow, or green based on values.

Enabling Rule Based Conditional Formatting

Instead of setting a bunch of rules, we can create a measure that specifies these colors. If you remember a few weeks ago, I did a post on using the SWITCH() function in DAX. I am going to use it to create a measure to specify the colors I want to use.

Field Formatting v1 = 
VAR SumRevenue = SUM(Revenue[Revenue])
RETURN
    SWITCH(TRUE(),
        SumRevenue < 1450000, "Red",
        SumRevenue < 1500000, "Yellow",
        SumRevenue >= 1500000, "Green",
        "Gray")

Note that I am using colors by name in my measure. It is not fancy, but it gets the job done. Once the measure is ready, we just need to apply it to the column. I select field as my format type and then select the measure I just created.

Setting Up Field Based Conditional Formatting

The result is my table formatted as expected.

Results from Field Based Conditional Formatting

And just like that we are using a measure to drive our conditional formatting.

This Looks Great! Can I Customize My Colors?

You can absolutely customize your colors! Instead of writing out the words, you can use hex codes to set the color.

Field Formatting v2 = 
VAR SumRevenue = SUM(Revenue[Revenue])
RETURN
    SWITCH(TRUE(),
        SumRevenue < 1450000, "#9c0a00",
        SumRevenue < 1500000, "#f7da00",
        SumRevenue >= 1500000, "#006616",
        "Gray")

Using a branding guide to help me, I pulled the red, yellow, and green hex codes to use in my report.

Field Based Conditional Formatting Using Hex Color Codes

But now my colors are a little too dark How do I fix that? Here’s a little pro tip – you can add two numbers on the end of your hex code to determine the transparency of the color. I wanted to soften the colors a bit and make it 50% transparent, so I added a 50 to the end of each hex code.

Field Formatting v3 = 
VAR SumRevenue = SUM(Revenue[Revenue])
RETURN
    SWITCH(TRUE(),
        SumRevenue < 1450000, "#9c0a0050",
        SumRevenue < 1500000, "#f7da0050",
        SumRevenue >= 1500000, "#00661650",
        "Gray")

And the difference is dramatic!

This makes a huge difference in our formatting and it is much easier to read.

Why Go Through All Of This Effort For Conditional Formatting?

If you are anything like me, you are probably wondering why you should go through this kind of effort to build out measures for conditional formatting. Every tool has a purpose and this is one that rarely gets leveraged. But consider some of these use cases where you might find it useful.

Complex Rules

When using rules, you find that you are put into a simple set of conditions that drive your formatting. Maybe you have a complex set of conditions that you need to consider. Instead of a simple greater than, less than, or between set of rules, you can build out some complex logic.

For example, you only want the colors to appear for the months in Q4. You cannot accomplish this with the standard set of rules. But with a little extra DAX, you can make that happen.

Field Formatting v4 = 
VAR SumRevenue = SUM(Revenue[Revenue])
RETURN
    SWITCH(QUARTER(MAX(Revenue[Month])) = 4,
    TRUE(), 
        SWITCH(TRUE(),
            SumRevenue < 1450000, "#9c0a0050",
            SumRevenue < 1500000, "#f7da0050",
            SumRevenue >= 1500000, "#00661650",
            "Gray")
    )

If you wanted to get really crazy, you can build out a complex measure that darkens the color the closer you get to the due date of a goal. The sky is the limit!

Color Flexibility

What if you had a report that can be filtered for multiple customers? You could setup your formatting to highlight values using their primary color color scheme.

If you have a customer table, you can specify some key palette colors and reference them in your SWITCH() statement. This enables a personal touch for each customer without having to create separate report pages or files for each one. Talk about scaling out your reporting!

Anything Else I Need To Know?

Remember, you are building out your conditional formatting using measures. You always want to optimize them to be efficient and not tax your report canvas. You are adding additional calculations to your report canvas, so don’t overdue it!

So how about you? Have you used field based conditional formatting before? If not, do you have a use case? If so, tell me about it in the comments below!

Draw Attention with Conditional Formatting

Conditional Formatting is a powerful element that enhances your reporting. When asked for feedback on a report, I often recommend users use some form of conditional formatting to help draw attention to key elements. I won’t lie to you – it takes extra effort to make it work. However, with a little planning you can make your reports pop while guiding your readers to the most critical information on the canvas.

Why is Conditional Formatting Important?

Conditional formatting takes good reports and makes them great. When you design a report canvas, it can quickly become cluttered with too much information. It overwhelms the viewer and causes anxiety. As a result, energy is wasted on trying to understand what the report says. Conditional formatting helps resolve this issue for the viewer.

Conditional Formatting In Your Car

Think about driving a car and looking at the dashboard. There are several measures and metrics to tell you what is happening. A speedometer tells you how fast you are traveling. A tachometer tells you how many revolutions your engine is making in a minute.

However, some of the gauges have a little help. For example, your fuel gauge tells you how much fuel is in your tank. It is a simple – when the gauge reaches “E” or “0”, it is time to get more fuel. But with the bustle of everyday life, sometimes we forget to check the gauge. That is when the low fuel light comes on and alerts us that we need to stop and fill up. The conditions are simple – when the fuel level reaches a low enough point, the light turns on.

Now, the light is not really necessary. It is not required for your car dashboard to work. Yet, we find it useful as it draws our eyes to a potential concern so we can resolve it. It alerts you to an issue that pairs with an action to resolve it.

The check engine light works in a similar fashion. It alerts you of a problem that you have to address. Unlike the low fuel light, the check engine just tells you there is a problem. You will have to do a little more research to find the issue. If you don’t have access to a scanner, you might need to take your car to a garage to have it checked out. Either way, you know something is wrong and can resolve it before it becomes a bigger problem.

Augmenting Reports with Conditional Formatting

Conditional formatting does not replace your current report visuals. It draws the eyes to key information on the canvas so you can act. With a little splash of color, you can draw eyes quickly to important data points.

Now, you might be asking “don’t I need a legend or something to tell people what they need to know?” Believe it or not, you really don’t need to do much. If you highlight a value in bright red, almost all viewers will understand that it is bad. When you highlight something in green, it is good. That does not take much effort to understand.

The trick is properly apply the conditional formatting so users are able to understand what is on the canvas.

Applying Conditional Formatting

So all of this sound great. But how do I apply it?

I find the easiest place to apply it is with a table or matrix visual. Even though Power BI has lots of rich visuals, many people still hold on to their PivotTables from Excel. They love seeing data in a tabular format. This is a great introduction to how to apply conditional formatting in a standard report.

For this article, we will be looking at a simple table that lists sales revenue by month for 2021. Our goal is to call out values that need attention so our consumers can understand them.

Table of Revenue by Month

Highs and Lows with Gradient Formatting

The table above lists out how much revenue was received per month. What if I asked you which month had the highest revenue. You are pretty smart, so you could figure it out fairly quickly. But what if the table had 5 years of revenue? It would take a considerable amount of brain power to figure it out. You could do it, but it would not be easy. Gradient formatting aides viewers in seeing which months had the highest revenue.

To start, we have to enable conditional formatting on the cell backgrounds. We will simply select the table visual, go to the visual properties, expand cell elements, select the revenue column, and turn on the background color element.

Enabling Conditional Formatting on a Table

You will see that we can easily identify July 2021 as the month with the highest revenue. We also can see February as the month with the lowest revenue. There is no denying it – conditional formatting makes it much easier to read!

But this just shows highs and lows. What if I have a revenue goal that I want to meet. Gradient conditional formatting does not meet that requirement. So how do we take our visualization to the next level?

Thresholds and Goals with Rule-Based Formatting

Instead of highlighting on a gradient scale, we can set formatting on a series of rules. To get started, select the function (fx) button under background color. This will pop open a new window to allow us to make our changes.

From here, we will change the Format Style from Gradient to Rules. After that, I needed to add two more rows of rules to achieve my goal. From there I can establish my rules to highlight which months did not reach my revenue goal of $1.5 million dollars.

The first rule is to show anything that missed my revenue goal. So I will start with zero to $1.45 million. I then assigned the color red to that field. Why not $1.5 million? While that is my goal, I want to highlight any revenue amounts that were close to my goal, but did not quite make it. So my next rule highlights anything between $1.45 and $1.5 million with yellow. And finally, anything over $1.5 meets the goal and is highlighted in green. However, I need a top end value for that rule, so I just put $5 million in there as a place holder.

Setting up Rule-Based Conditional Formatting

With my rules in place, it is much easier to tell which months met or missed the goal of $1.5 million in revenue.

Highlighting Values with Rule-Based Conditional Formatting on a Table

It is easy to see that we hit our revenue goals in eight months of 2021. We missed our goals in February, April, and May. And while we were close, we did not quite reach our goal in June. This formatting draws our eyes and makes it easy to where we missed the mark so we can adjust our plans for 2022.

Can I Format the Entire Line?

The short answer is yes – you can do that. The only catch is that you have to update the rules for each individual column in your table. Just select the Month column and start making your changes.

The biggest thing you need to do is change the field the formatting is based upon. It defaults to the column you are formatting. However, we need to set it based upon the values in the revenue column.

Formatting Conditional Formatting Rules for the Month Column using Revenue

Set the rules to match your original formatting and you will have the entire line formatted.

Conditional Formatting Applied to All Columns in a Table

With all of this in place, I have provided conditional formatting leveraging the background color. This was easy because I only have two columns in this table. If I had a lot of columns, it might be more time consuming to setup.

Now, as nice as background colors are, I am constantly asked if there any other options out there. The answer is of course!

What Else Can I Do with Conditional Formatting?

I like using a cell’s background color but there are other options out there. If you want to use color, but the cell background is too bold, then you can use font color instead.

Conditional Formatting Using Font Colors

One of my personal favorites is to use icons with the report.

Conditional Formatting Using Icons

With icons, you can select different styles that suit your needs. You also have the ability to move the icon to the left or right of the values in the table. And if the occasion calls for it, you can just use an icon in the field instead of a value.

Setting Up Conditional Formatting Using Icons

The other favorite is to leverage data bars. This aligns better with gradient conditional formatting, but helps show magnitude of the values better.

Conditional Formatting Using Data Bars

Regardless of what style of conditional formatting you use, I promise you it will elevate your reports and make them easier to understand.

Next Steps with Conditional Formatting

So with this knowledge of conditional formatting, should I know anything else? Of course – the biggest thing is knowing when to use conditional formatting. Quality report design rules still apply. Overuse of conditional formatting can cause your report canvas to look cluttered and confusing. It also could impact performance as calculations are taking place in the background to enable the formatting. Just remember that less is more.

The other thing you know is that conditional formatting can be applied to more than cells in a table or matrix. In fact, there are other elements of conditional formatting to share. I have an upcoming article that will dig into those features, so keep an eye open for it!

Until then, have you used conditional formatting? Do you have a use case ready to go? If so, tell me in the comments below!

Using the POST Method with REST APIs Inside Power BI

Earlier this week I spoke about the Basics of Connecting to REST APIs at the Global Power BI Summit. A lot of people found it valuable, but wanted to know more about how to use the POST method to get data. Specifically, they wanted to know how to add a payload with the request to make it work. Since there was a lot of interest, I decided to write an article on this topic and share it with everyone.

For this article, I will be using the Microsoft OAuth2 endpoint to get an authorization token. You can find detailed documentation on the endpoint here. I will not be going into the details of the process to create an app registration or apply it in the Microsoft environment as that will take some time to cover. Instead, I decided to focus on the most important part of the request – the POST Method!

No/Low Code No More!

For many, this will be the scariest part of the entire process. This might be your first time ever writing code for a query from scratch. We do not have a connector to help us with this process. Fear not as we will walk through this entire process together and break it down. So to get everything started, go to Get Data and select Blank Query.

Adding a Blank Query to your report.

Once you select Blank Query, you will be redirected into Power Query with a blank canvas. I like to quickly update the query name on the right hand side before I forget. I am calling mine “Get-AuthToken”. Once that is in place, we will open up the Advanced Editor in the home ribbon. That is where the real fun will begin…

POST Method Query

Now that the Advanced Editor open, we are going to start writing out the query. We will need to write out a web connector query and a message body to make this work. I like to start with the web connector and build out from there.

Building the Initial Connection Query

Currently, we have Source = “” in our editor window. Those quotes are going to be the base of our query. We are going to start by adding the endpoint url in between the quotes. We should see something that looks like this:

let
    Source = "https://login.microsoftonline.com/<TENANTID>/oauth2/v2.0/token"
in
    Source

In order to specify how we are connecting to the endpoint, we will wrap it with Web.Contents:

let
    Source = Web.Contents("https://login.microsoftonline.com/<TENANTID>/oauth2/v2.0/token")
in
    Source

After that is in place, we need to add our headers. These are specified in the documentation and will be unique to your API. This example uses two headers and they are required to achieve the desired result. Note the comma between the url and the headers.

let
    Source = Web.Contents("https://login.microsoftonline.com/<TENANTID>/oauth2/v2.0/token", [Headers=[Accept="application/json", ContentType="application/x-www-form-urlencoded"]])
in
    Source

Lastly, because the header is specifying the response in JSON, we will wrap the entire string with Json.Document to translate the response.

let
    Source = Json.Document(Web.Contents("https://login.microsoftonline.com/<TENANTID>/oauth2/v2.0/token", [Headers=[Accept="application/json", ContentType="application/x-www-form-urlencoded"]]))
in
    Source

With that code in place we have our base query built. If you want to save some typing, you could always use an existing query as a template. Just copy and paste it into the blank query and make your changes.

Now that we have the base query written out it is time to focus on the message body.

Body Building without Protein

The body comes by a few different names. You might see it as message body, query, payload, or simply “The Data”. Essentially, it is additional information that is passed so the endpoint works as expected. While you don’t have to lift weights for this step, you might find it just as frustrating. I have tried this a few different ways, but found this to be the easiest method.

The first thing we will do is create a line right above our Source line and below the let line. From here, we will enter ContentBody = “”, as a place holder. Make sure you add the comma at the end of the line!

let
    ContentBody = "",
    Source = Json.Document(Web.Contents("https://login.microsoftonline.com/<TENANTID>/oauth2/v2.0/token", [Headers=[Accept="application/json", ContentType="application/x-www-form-urlencoded"]]))
in
    Source

My example requires five different fields be specified in the body. The formula is simple: field name = field data. We will add an ampersand at the end of the line and return to the next line to make it easier to read.

let
    ContentBody = 
        "Content-Type=application/x-www-form-urlencoded&
        scope=https://graph.microsoft.com/.default&
        grant_type=client_credentials&
        client_id=<CLIENTID>&
        client_secret=<CLIENTSECRET>",
    Source = Json.Document(Web.Contents("https://login.microsoftonline.com/<TENANTID>/oauth2/v2.0/token", [Headers=[Accept="application/json", ContentType="application/x-www-form-urlencoded"]]))
in
    Source

As a best practice, I keep elements that might need to be changed at the bottom for easier access. This example leverages a client secret that will need to be updated over time. To make it easier to access, I leave it at the bottom.

Add the Body to the Web Connector

To finalize your query using the POST Method, you have to add the message body to the source line. We need to pass the body as content. However, I need to convert it into binary. We are just going to add Content=Text.ToBinary(ContentBody) inside of the first set of brackets so it passes the body in the web connector.

let
    ContentBody = 
        "Content-Type=application/x-www-form-urlencoded&
        scope=https://graph.microsoft.com/.default&
        grant_type=client_credentials&
        client_id=<CLIENTID>&
        client_secret=<CLIENTSECRET>",
    Source = Json.Document(Web.Contents("https://login.microsoftonline.com/<TENANTID>/oauth2/v2.0/token", [Content=Text.ToBinary(ContentBody), Headers=[Accept="application/json", ContentType="application/x-www-form-urlencoded"]]))
in
    Source

Now that everything is in place, we can click Done and let the query do its magic!

Response from my POST Method query that brings back an access token for authentication.

To take things to the next level, I can drill down on the actual token by right clicking and selecting drill down. Next I will convert the query to a function by adding ()=> above the let line. In doing so, I will be able to leverage it for multiple endpoints that require token based authentication with a simple call.

()=>
let
    ContentBody = 
        "Content-Type=application/x-www-form-urlencoded&
        scope=https://graph.microsoft.com/.default&
        grant_type=client_credentials&
        client_id=<CLIENTID>&
        client_secret=<CLIENTSECRET>",
    Source = Json.Document(Web.Contents("https://login.microsoftonline.com/<TENANTID>/oauth2/v2.0/token", [Content=Text.ToBinary(ContentBody), Headers=[Accept="application/json", ContentType="application/x-www-form-urlencoded"]])),
    access_token = Source[access_token]
in
    Source

Make Life Easier with Parameters

I plan to write a more detailed article on Parameters inside Power Query but I want to share a little tip that will make your life much easier. With this example, we are connecting to an app registration inside Azure Active Directory through a Client ID and Client Secret. The Client Secret will expire at some point and needs to be refreshed. To make your life easier, you can add your dynamic values as Parameters so you can update the query quickly.

Go to your home ribbon and click Manage Parameters. Click new and add your parameter name. Lastly, add your parameter value and click OK when finished.

Setting up parameters to simplify the process to update my client secret.

Now we just need to replace the values in our query. We will have to use the ampersands to concatenate our values together so they come together in a single string.

()=>
let
    ContentBody = 
        "Content-Type=application/x-www-form-urlencoded&
        scope=https://graph.microsoft.com/.default&
        grant_type=client_credentials&
    	client_id=" & ClientId & "&
        client_secret=" & ClientSecret ,
    Source = Json.Document(Web.Contents("https://login.microsoftonline.com/" & TenantId & "/oauth2/v2.0/token", [Content=Text.ToBinary(ContentBody), Headers=[Accept="application/json", ContentType="application/x-www-form-urlencoded"]])),
    access_token = Source[access_token]
in
    access_token

Now if I need to update my client secret, I can just update the parameter instead of breaking open the Advanced Editor. I can even do it from the settings view in the Power BI Service. But that will have to wait for another article.

Final Thoughts on the POST Method

It is important to note that not all POST Method endpoints are meant for querying in Power BI. The primary purpose of the method is to create items. However, it is not unheard of for querying rows of data. Before you do anything with an endpoint that uses the POST Method make sure you review the documentation. You might accidentally write data to your endpoint if you are not careful!

Do you have a use case for querying using the POST Method? Any favorite endpoints? If so, leave a comment below!

SWITCH()-ing Up Your Conditional Statements

Excel Power Users are fans of the classic IF() statement. Powerful as it may be, it can get quite confusing when you nest them together. If you have never used the SWITCH() function in DAX, then you are missing out on an opportunity to simplify your conditional statements.

Complications of IF() Statements

Like you, I have used IF() for years. It has served me well and I have relied upon it for years. However, I always found it cumbersome when I needed complicated logic.

Let’s start with a basic list of vehicle models manufactured by Ford. If I wanted to quickly flag a particular model, I could do so with a basic IF() statement.

Ford F-150 Flag = 
IF ('Model List'[Model] = "F-150", "Yes", "No")
List of models produced by Ford Motor Company

Where it gets complicated is when you nest your IF() statements. If I wanted to categorize the vehicles by type (truck, SUV, car, etc.), this would require a more complicated statement.

Vehicle Type IF = 
IF ('Model List'[Model] = "F-150", "Truck",
IF ('Model List'[Model] = "F-250", "Truck",
IF ('Model List'[Model] = "F-350", "Truck",
IF ('Model List'[Model] = "Ranger", "Truck",
IF ('Model List'[Model] = "Maverick", "Truck",
IF ('Model List'[Model] = "Mustang", "Car",
IF ('Model List'[Model] = "Explorer", "SUV",
IF ('Model List'[Model] = "Escape", "SUV",
IF ('Model List'[Model] = "Expedition", "SUV",
IF ('Model List'[Model] = "Edge", "SUV",
IF ('Model List'[Model] = "Bronco", "SUV",
IF ('Model List'[Model] = "Transit", "Van", "Error" 
))))))))))))

While effective, it becomes complicated. The DAX is bulky and requires you to maintain a number of parentheses to complete the formula. While harmless on its own, it could become cumbersome if you need to interject this into a larger formula.

Now, you might be reading this code and say “Hey Dom! There is a way to simplify this IF() statement! ” You are correct and I will review those methods shortly but let’s keep the statement simple for now.

SWITCH() It Up and Clean It Up

I can take that basic IF() statement and simplify it using the SWITCH() function. This function allows you to simplify the logic so it is easier to manage. The basic syntax of the function is:

SWITCH(<expression>, <value>, <response> [, <value>, <response>, … ] [, <else>])

Simply put, this function will evaluate an expression and compare it to a list of values and provide the appropriate response. It also contains an else clause which fills in any other gaps you might have. If we apply it to our complicated IF() statement, we can simplify it to this:

Vehicle Type SWITCH =
SWITCH ('Model List'[Model],
    "F-150", "Truck",
    "F-250", "Truck",
    "F-350", "Truck",
    "Ranger", "Truck",
    "Maverick", "Truck",
    "Mustang", "Car",
    "Explorer", "SUV",
    "Escape", "SUV",
    "Expedition", "SUV",
    "Edge", "SUV",
    "Bronco", "SUV",
    "Transit", "Van",
    "Error"
)

Now that is much easier to read and understand. The SWITCH() function will evaluate which model is selected and then check it against a list of values. If the model is found in the list, then it will bring back a response. If the model is missing, then it will bring back “Error” as specified in the else statement.

Same results with different functions

But the really cool part about the SWITCH() function is that we can flip it upside down. Instead of specifying an expression and checking it against a value, we can specify a value and check it against a list of expressions. Let’s say we want to bucket the price of the F-150 line of trucks. Since there are different trim packages, the range is quite considerable. We can use SWITCH() to accomplish this goal.

Price Buckets = 
SWITCH (TRUE (),
    'Model List'[Price] <= 36000, "Low",
    'Model List'[Price] <= 55000, "Mid",
    'Model List'[Price] > 55000, "High"
)
Prices bucketed by SWITCH()

In this expression, the value we are looking for is TRUE(). From there, our list of expressions will come back either TRUE() or FALSE(). I can still accomplish the same with an IF() statement, but this is so much cleaner.

Price Buckets =
IF ('Model List'[Price] <= 36000, "Low",
    IF ('Model List'[Price] <= 55000, "Mid",
        IF ('Model List'[Price] > 55000, "High"
        )
    )
)

Both methods work well, but the SWITCH() function keeps the code clean. This will be important as we start to build more complicated measures.

SWITCH()-ing Directions

So far we have only been working with simple logic statements. However, sometimes these logic statements become far more complicated. In our list of F-150s, there are two models which are classified as emergency vehicles. They are not to be sold to the general public. We also have a sport and electric trucks that are not part of our standard report. As a result, we want to clean up our reporting and remove them from our pricing buckets. Let’s try a few different ways to manage this.

Option 1 – Add Model Types to the SWITCH() Statement

The easiest option to start is to add the model types to the SWITCH() statement we used above. This will require us to add three values to our SWITCH statement to clean up the list.

Price Buckets v1 = 
SWITCH (TRUE (),
    'Model List'[Model] = "Emergency", "N/A",
    'Model List'[Model] = "Electric", "N/A",
    'Model List'[Model] = "Sport", "N/A",
    'Model List'[Price] <= 36000, "Low",
    'Model List'[Price] <= 55000, "Mid",
    'Model List'[Price] > 55000, "High"
)

There is nothing wrong with this method as it works well, but we can simplify those three lines into a single line.

Option 2 – AND/OR Modifiers

In the first section I mentioned there was a way to simplify our IF() statements. Excel users are very familiar with using AND()/OR() statements to handle multiple logic arguments at once. The only catch is that these functions can handle two arguments at once. Since we have three vehicle types to consider, we will need to chain two items together.

Price Buckets v2 = 
SWITCH (TRUE (),
    OR ('Model List'[Model] = "Emergency" OR ('Model List'[Model] = "Electric", 'Model List'[Model] = "Sport")), "N/A",
    'Model List'[Price] <= 36000, "Low",
    'Model List'[Price] <= 55000, "Mid",
    'Model List'[Price] > 55000, "High"
)

This method works, but like the IF() statements, they get complicated very quickly. This is not clean and hard to maintain if you want to modify the list of model types in the future.

Option 3 – Using AND/OR Without Functions

A hidden secret with conditional statements is how to use AND/OR without the function. We can substitute AND() with “&&” to chain as many statements together as we want. Also, we can substitute OR() with “||” and accomplish the same thing. This eliminates the need to manage the parentheses associated with the functions

Price Buckets v3 = 
SWITCH (TRUE (),
    'Model List'[Model] = "Emergency" || 'Model List'[Model] = "Electric" || 'Model List'[Model] = "Sport", "N/A",
    'Model List'[Price] <= 36000, "Low",
    'Model List'[Price] <= 55000, "Mid",
    'Model List'[Price] > 55000, "High"
)

This method still works well, but it is still not the cleanest way to keep everything together.

Option 4 – Combine SWITCH() Statements

Since we have a specific list of vehicle types that should not be bucketed, we can use one statement to handle which vehicle types should not be bucketed while another assigns buckets to vehicles that can be bucketed.

Price Buckets v4 = 
SWITCH (TRUE (),
    ( 'Model List'[Model] ) IN { "Emergency", "Electric", "Sport" }, "N/A",
    SWITCH (TRUE (),
        'Model List'[Price] <= 36000, "Low",
        'Model List'[Price] <= 55000, "Mid",
        'Model List'[Price] > 55000, "High"
    )
)

In this sample code, we have used two SWITCH() functions. The first function simply checks the vehicle model information. I even cleaned up the expression to evaluate the vehicle model similar to a IN expression found with SQL statements.

I then took a second function that came through in the else statement to evaluate the price bucket for models not already dispositioned in the first SWITCH() function.

Thinking About SWITCH()-ing?

Now, I will admit that I am complicating the formula in that last option. But I wanted to show how easy it can be to inject a SWITCH() function inside of an existing function. It took me a while to get used to using SWITCH(). In fact, before writing this article, I still used IF() because I did not think I could chain AND()/OR() functions. After trying it out for this article, I even learned that I can probably eliminate IF() from most of my DAX formulas.

Are you using SWITCH() at all with your DAX formulas? Do you think it is easier than IF()? Tell me in the comments below!

Mobile Reporting That You Can Actually Use!

The February 2022 update of Power BI came with a major change for mobile reporting. It was so exciting that I dropped my original article for this week so I could share this update. Mobility has always been a part of Power BI dashboards, but there were some challenges. It was not flexible and did not offer much with visual interaction.

Mobile reporting was then added to Power BI reporting, but there was still one major challenge. What worked for consumers on a computer or tablet did not work for mobile phones. To make it functional, you might end up creating two different versions of the report to accommodate mobile users.

With the February 2022 update of Power BI, we can finally take an existing report and modify it for a quality mobile experience. This is a huge step forward for the frontline and I cannot wait to use it!

Data on the Frontline

Data gurus pontificate about how leaders should make data driven decisions. I agree with them. I love having the right data to make decisions. With some analysis, I have likely avoided making poor purchases because of my research.

As wonderful as many of these data gurus are with their ideas, there seems to be a prejudice towards frontline leaders. The gurus spend their time reviewing data about what happened and how they can turn their insight analytics to prescriptive analytics. But the frontline workers are hungry for data and welcome it for making decisions.

Moving and Shaking on the Frontline

Frontline leaders spend their day on their feet. They are normally walking the floor pitching in where needed. If they are in retail, sometimes they are running registers while associates are taking breaks. In manufacturing they could be helping to repair an issue with a line so production can resume.

Frontline leaders likely have a desk somewhere in the facility, but they rarely sit at it. Sometimes leaders might need to share a computer at that desk. During my time working retail during college, I worked at a store that had four managers and two supervisors who all shared the same computer. If they wanted to pull a report or review some data, they had to wait in line. If content creators only create reports that must be viewed on a computer, they will never get used.

Decision Making on the Floor is Paramount

Frontline leaders are constantly making decisions when walking the floor. Restock orders, new material requests, and resource management are all made in real time. If you want to enable your user leaders to make data driven decisions, the data needs to be with them on the floor.

Now you might be thinking “Hey Dom! Laptops are portable. They should just carry one with them at all times on the shop floor.” And that is a good point – a laptop in hand is fairly portable. However, there is a risk factor. You need to find a solid surface to set the machine on so you can use it. You also run the risk of dropping it or setting it down and forgetting it.

This is where a mobile phone becomes the star. It is compact, powerful, and convenient. Properly managed, this will enable your frontline leaders to have mobile reporting with them wherever they go. And before you say anything about the risks of dropping or misplacing a laptop are the same for a phone, I will challenge you and say it is not. Ruggedized phone cases are easy to find and people tend to not misplace their phone. Even better, if a company has a bring your own device (BYOD) policy, your leaders can use their own device. They tend to protect it more than company issued hardware and don’t want to be without it.

Know Your Audience

One of the challenges of content creators is understanding the frontline audience. Like many creators, I love putting together flashy reports and combining data for deeper insights. This is great content for consumers who are looking to learn more from the data and have time to pour over reports.

Frontline leaders tend to be more focused on current operations. They want to review data to decide what needs to happen next. As a result, frontline leaders want reports that have key metrics and measures that fulfill the immediate needs on the floor. Instead of fancy reports that show how production relates to the relative position of Mars, they need to know how they are trending to plan and what they can do to meet it.

Now frontline leaders still care about the insights found in data. They still have access to computers and will want to perform a deeper analysis on their data. The key is making sure you have the right tool for the right job.

Now that you understand why I think mobile reporting is so important, let’s start building one out.

Create your own Mobile Reports

The first thing you will need to do is update your Power BI Desktop client to the February 2022 release. If you have not done so, you can download it here. This will allow you to use some of the new mobile reporting features that we will discuss in this article. After you have updated Power BI Desktop, open up an existing report hat you want to enable for mobile use.

Once you have your report finished, go to the view ribbon. From there, you can click on the Mobile Layout button to bring up the mobile view. You will notice that you will see a new blank canvas in the shape of a mobile phone.

Switching to the Mobile Reporting layout

While the canvas is blank, you will note that there is a pane on the right hand side full of visuals from your report page. We now can drag and drop visuals onto the mobile report canvas and adjust them accordingly.

Editing the Mobile Reporting canvas

As a result, you can optimize your existing report for mobile devices. Note that I did not add my table of top fifteen products sold. It is great info, but hard to read on your mobile device. We will make it available for consumers from their computers, but suppress it on a mobile device.

Take Mobile Reporting to the Next Level

You might be saying “Big deal! This has been around for a while! Why are you writing an article about this?!” But here is what I am loving about this update – we now have the ability to customize our visuals in the mobile format. When you select a visual on the mobile canvas, a new pane is available on the right side.

Think about this scenario – you have a bar chart but want to see what the actual values are. If you are on your computer, you can hover to discover what the actual amount is on the bar. But with mobile reporting, it is far more difficult to accomplish that. So to help consumers see what is happening, I can now add a data label without disturbing my original report.

To add data labels to by bar chart, I simply select it and expand the visualization pane. From there, turn on the data labels and customize accordingly. Note the new little icon that appears – the Power BI logo with a mobile device over it. This indicates that the visual is customized for the mobile experience.

Customizing the bar chart to use data labels

The best part of this whole thing is when I go back to the regular report view, the data labels will remain hidden!

Original report experience as seen on a computer

And just like that I have been able to optimize my mobile reporting experience while maintaining my original report! Amazing!

But Before You Change Your Mobile Reporting Experience…

Really make sure you know what you want to change! Once you break away from the original report configuration, you now have to update both the desktop and mobile report canvas. Even worse, there is no clear indicator that a report has been optimized for mobile users. As you consider your mobile reporting controls and lifecycle plans, you might want to consider options that ensure the mobile canvas is properly updated.

So have you built out any mobile reports? Do you have any great use cases for mobile reporting? If so, tell me in the comments below!

How do I connect to REST APIs with Power BI? (Part 4)

In my last article, I shared how to scale a query by using a function in Power Query. However, I noted there might be some challenges with this approach and considerations you should keep in mind when connecting to REST APIs. Some of these considerations are simple and are similar to normal query optimization activities. While not as technical as other articles, I feel it is important to take some time to focus on potential challenges you might encounter when leveraging REST APIs.

Limits, Throttling, and Speed

When reading your documentation, you might see words such as “limits” and “throttling” and wonder what they are. You will build your report and everything will be fine until one day your refresh fails. In reviewing the refresh errors it will suddenly make sense what “limits” and “throttling” mean.

To ensure APIs are not overburdened, limits and throttling are employed to prevent abuse. This protects the endpoints and provides a reliable service. Understanding how to navigate them are essential to prevent your refreshes from failing as your solution scales.

What are API Request Limits?

API limits restrict the number of calls made in a day, week, or month. If you signed up for the Yahoo Finance API, you notice they have three tiers of membership. The free tier limits your calls to 100 per day. Because of how we built our function, you would have to limit your report to 100 stocks and only refresh it once a day.

Now there are additional tiers that you can purchase which increase your limits. The Yahoo Finance API offers tiers that give you 20,000 or 75,000 calls per month which might help accommodate your use case but requires an additional financial investment. But even if you increase your subscription, note that these tiers still have a hard limit. Meaning that you might need to keep purchasing a higher tier if you blow through your request limit.

Subscription pricing and limits for the Yahoo Finance API (as of February 2022)

To be a good steward of your subscription, you will want to regularly monitor your API usage and verify that you are not burning through your quotas too quickly. It never fails – you run out of requests just when you need the data the most.

What is Throttling?

APIs use throttling to protect endpoints from being heavily queried over a short period of time. Queries are limited to the number of calls made per minute, per 15 minute window, or per hour. This protects endpoints from rogue code that could degrade the service because of abuse.

With the Yahoo Finance API, our documentation states that there is a rate limit of 300 calls per minute. If you had a list of 1,200 stocks it would take at least four minutes for the refresh to complete. (1,200 stocks divided by 300 = 4 minutes) Just like how every API is unique, they manage throttling differently.

In case you missed it above, you can see the rate limit for each subscription tier.

Last year I built a Power BI report that leveraged Google’s mapping APIs to get coordinates and street views of addresses in my town. Google enacted throttling to limit requests to 100 per minute. When refreshing my data, I hit my 100 request limit and the API paused until the full minute had passed. In doing so, I was able to proceed with the refresh. With 3,500 houses in town, it took 35 minutes for the refresh to complete. It was slow, but it worked great.

On the other side of the spectrum, the Microsoft Graph APIs have a limit of 10,000 calls per 10 minute period. When I ran over this limit, instead of the API pausing, I ended up receiving responses with a 429 error stating I reached the throttling limits. In order to manage this, I had to get a little more creative with my query to make sure I did not receive that error.

Impact on Refresh Time

Regardless of limits, you will need to consider refresh time when using REST APIs. If your primary data sources reside in SQL servers, this will be an adjustment for you. Normally you just cast a wide net with your query and bring in everything you can and work from there. With APIs, you want to narrow that scope to only what you really need. But with REST APIs, the refreshes tend to be slower. Adding some filtering will speed up your queries and make them more efficient.

If you are in a situation where you need a large amount of data from an API, you might want to consider leveraging Azure Data Factory or Databricks to incrementally replicate that data into a data warehouse instead.

Paging Queries

For this series of articles, our endpoint was fairly basic. It only brought back one row of data for each stock symbol. This was by design because our overall goal was to cover the basics. As you explore other APIs, you might find endpoints that bring back several rows at a time.

If you expand your horizon with REST APIs, eventually you are going to run into something called paging. Simply put, paging is the splitting of responses over multiple queries. Similar to limits, this is to prevent overburdening the endpoint with your query.

As an example, let’s say you are querying an endpoint that has 50,000 records. Paging has been enabled on this query and is limiting the responses to 1,000 records at a time. In order to receive all 50,000 records, you will need to build logic into your query to pull each page individually.

So how do you know your endpoint is using paging? It is all about the documentation. If an endpoint has pagination, there are usually a few parameters that will tip you off. If you see a parameter called “PageNo”, “PageSize”, or “Top”, you could run into paging.

It is also important to consider that you could avoid pagination with quality filtering. If your request is targeted and removes the noise of other records, it could avoid this problem all together.

In the coming weeks I will be doing an article specifically on paging, so keep an eye out for it!

Token Authentication

Another challenge you might encounter is token based authentication. Instead of passing an API key for authentication, you would need to call another endpoint to receive a token. Once you have the token, you would pass it through a header.

If you encounter this situation, you would need to create a function in Power Query that would solely return a token. While it takes a few extra steps, it is likely reusable with other endpoints in your report. In addition to that, it will simplify your query code and keep it tight and compact.

Conclusion

Congratulations for making it to the end of this series. There are a lot of challenges when implementing REST APIs as a data source but the juice is worth the squeeze. Taking yourself out of the mix and automating my refreshes is the end goal. And while the experience may not be as efficient as a SQL data source, it still gets the job done.

Have you been following along? Were you able to successfully connect and extract data from Yahoo Finance? How about other REST APIs? Let me know in the comments below!

How do I connect to REST APIs with Power BI? (Part 3)

In my last article, I showed you how to connect the quote endpoint from the Yahoo Finance API to Power BI. By leveraging the documentation and using copy/paste, we successfully connected to the API without a single line of code. In our review of the outcome we noted that our response only brought back data for a single stock.

While helpful, we know you probably want to refresh data for more than one stock. In fact, I happen to have a list of stocks that I am currently watching:

A list of stocks and when I wish I had bought them back in the day.

My next challenge is to take my original query and make it sustainable for however many stocks I have on my watch list.

Options are Plentiful

As with every Microsoft product, I have several different ways that I could manage this challenge. All have their benefits and drawbacks. Let’s look at them each and see which one would work best for my scenario.

Option 1 – Append Individual Queries

The first solution you might have considered is building a separate connection for each of the individual stocks we have in our list. Each query would be individually created and then appended into a master list so that way it stays organized. This solution would work and be effective, but there are drawbacks.

First of all, the biggest draw back is that I have to manually create this query each and every time I want to add a new stock. This does not take a ton of time, but it would require a few minutes of effort each and every time. Not a big deal if you only need to add one or two stocks to the model but it would add up with 20-30 stocks.

The other drawback is that you will have to continue managing the data model. If the list of stocks is dynamic, that means you either need to create a single model with every single stock in it or continue to manage it over time. This is not really sustainable as new stocks enter the market on a regular basis and requires manual maintenance.

Option 2 – Go Back to the Documentation

Sometimes you have some options when it comes to your endpoints for expanding your query. If we go back to look at our documentation for the quote endpoint, you will see that we can specify up to 10 stock symbols in our query.

Note that our API documentation says you can select up to 10 stock symbols all separated with a comma.

Perfect! So instead of having to write out 25 separate queries, I can condense it all into three. That is much more manageable and efficient! Well – almost more efficient…

Remember what I stated above – because we are still specifying the stock symbols for each query we still have to manually intervene every time we make a change to our stock list. This could get difficult especially as we go to maintain our list over time as we might need to remove stocks later. As a result, we will spend time searching for which query to resolve.

Option 3 – Scale Out our Original Query as a Function

The last option we have is to make our current query scalable. How do we make a query scalable? By converting it into a function.

Functions allow us to reuse that query over and over again to simplify our refresh. Acting similar to our first option, we can apply a function to a list of stock symbols and quickly bring back the quote information. Now instead of managing the queries, we are just managing a list of stock symbols. As symbols are added and removed, our data model adjusts accordingly. The result allows us to automate refreshes without having to adjust the data model in the future.

Converting a Query to a Function

Remember how I said you might need some minimal coding? That time has come. We will need to do something you may have never done before – open the advanced editor window in Power Query.

Before we do that, I do want to rename the current query to “Get-StockInfo” so it is a little easier to read. Just update the name on the query settings bar on the right. I like using the word get in front to help quickly identify it as a function versus a query.

Once you have done that, click on Advanced Editor in the home ribbon and strap in for the ride!

Step 1 – Convert the Query to a Function

Our goal is to make this function flexible for any stock symbol. That means we need to require a stock symbol to be called every time the function is leveraged.

When you open the code editor, the first line has the single word “let”. We are going to insert a row above that and put a pair of parentheses followed by an equals sign and greater than sign. This effectively makes the conversion from a query to a function.

()=>
let
    Source = Json.Document(Web.Contents("https://yfapi.net/v6/finance/quote?region=US&lang=en&symbols=MSFT", [Headers=[Accept="application/json", #"X-API-KEY"="bHjSKbiRtr4cOzmOopdrI5TUKX5kMKXU6weUteeL"]])),
    #"Converted to Table" = Table.FromRecords({Source}),
    #"Expanded quoteResponse" = Table.ExpandRecordColumn(#"Converted to Table", "quoteResponse", {"result", "error"}, {"quoteResponse.result", "quoteResponse.error"}),
    #"Expanded quoteResponse.result" = Table.ExpandListColumn(#"Expanded quoteResponse", "quoteResponse.result"),
    #"Expanded quoteResponse.result1" = Table.ExpandRecordColumn(#"Expanded quoteResponse.result", "quoteResponse.result", {"language", "region", "quoteType", "quoteSourceName", "triggerable", "currency", "firstTradeDateMilliseconds", "postMarketChangePercent", "postMarketTime", "postMarketPrice", "postMarketChange", "regularMarketChange", "regularMarketChangePercent", "regularMarketTime", "regularMarketPrice", "regularMarketDayHigh", "regularMarketDayRange", "regularMarketDayLow", "regularMarketVolume", "regularMarketPreviousClose", "bid", "ask", "bidSize", "askSize", "fullExchangeName", "financialCurrency", "regularMarketOpen", "averageDailyVolume3Month", "averageDailyVolume10Day", "fiftyTwoWeekLowChange", "fiftyTwoWeekLowChangePercent", "fiftyTwoWeekRange", "fiftyTwoWeekHighChange", "fiftyTwoWeekHighChangePercent", "fiftyTwoWeekLow", "fiftyTwoWeekHigh", "dividendDate", "marketState", "earningsTimestamp", "earningsTimestampStart", "earningsTimestampEnd", "trailingAnnualDividendRate", "trailingPE", "trailingAnnualDividendYield", "epsTrailingTwelveMonths", "epsForward", "epsCurrentYear", "priceEpsCurrentYear", "sharesOutstanding", "bookValue", "fiftyDayAverage", "fiftyDayAverageChange", "fiftyDayAverageChangePercent", "twoHundredDayAverage", "twoHundredDayAverageChange", "twoHundredDayAverageChangePercent", "marketCap", "forwardPE", "priceToBook", "sourceInterval", "exchangeDataDelayedBy", "pageViewGrowthWeekly", "averageAnalystRating", "tradeable", "priceHint", "exchange", "shortName", "longName", "messageBoardId", "exchangeTimezoneName", "exchangeTimezoneShortName", "gmtOffSetMilliseconds", "market", "esgPopulated", "displayName", "symbol"}, {"language", "region", "quoteType", "quoteSourceName", "triggerable", "currency", "firstTradeDateMilliseconds", "postMarketChangePercent", "postMarketTime", "postMarketPrice", "postMarketChange", "regularMarketChange", "regularMarketChangePercent", "regularMarketTime", "regularMarketPrice", "regularMarketDayHigh", "regularMarketDayRange", "regularMarketDayLow", "regularMarketVolume", "regularMarketPreviousClose", "bid", "ask", "bidSize", "askSize", "fullExchangeName", "financialCurrency", "regularMarketOpen", "averageDailyVolume3Month", "averageDailyVolume10Day", "fiftyTwoWeekLowChange", "fiftyTwoWeekLowChangePercent", "fiftyTwoWeekRange", "fiftyTwoWeekHighChange", "fiftyTwoWeekHighChangePercent", "fiftyTwoWeekLow", "fiftyTwoWeekHigh", "dividendDate", "marketState", "earningsTimestamp", "earningsTimestampStart", "earningsTimestampEnd", "trailingAnnualDividendRate", "trailingPE", "trailingAnnualDividendYield", "epsTrailingTwelveMonths", "epsForward", "epsCurrentYear", "priceEpsCurrentYear", "sharesOutstanding", "bookValue", "fiftyDayAverage", "fiftyDayAverageChange", "fiftyDayAverageChangePercent", "twoHundredDayAverage", "twoHundredDayAverageChange", "twoHundredDayAverageChangePercent", "marketCap", "forwardPE", "priceToBook", "sourceInterval", "exchangeDataDelayedBy", "pageViewGrowthWeekly", "averageAnalystRating", "tradeable", "priceHint", "exchange", "shortName", "longName", "messageBoardId", "exchangeTimezoneName", "exchangeTimezoneShortName", "gmtOffSetMilliseconds", "market", "esgPopulated", "displayName", "symbol"})
in
    #"Expanded quoteResponse.result1"

However, we have not done anything to handle the requirement to pass a stock symbol when the function is called. To require a stock symbol be called, we will simply add a variable called StockSymbol and specify that it be passed in a text format. This will require a stock symbol be passed any time you leverage this function.

(StockSymbol as text)=>
let
    Source = Json.Document(Web.Contents("https://yfapi.net/v6/finance/quote?region=US&lang=en&symbols=MSFT", [Headers=[Accept="application/json", #"X-API-KEY"="bHjSKbiRtr4cOzmOopdrI5TUKX5kMKXU6weUteeL"]])),
    #"Converted to Table" = Table.FromRecords({Source}),
    #"Expanded quoteResponse" = Table.ExpandRecordColumn(#"Converted to Table", "quoteResponse", {"result", "error"}, {"quoteResponse.result", "quoteResponse.error"}),
    #"Expanded quoteResponse.result" = Table.ExpandListColumn(#"Expanded quoteResponse", "quoteResponse.result"),
    #"Expanded quoteResponse.result1" = Table.ExpandRecordColumn(#"Expanded quoteResponse.result", "quoteResponse.result", {"language", "region", "quoteType", "quoteSourceName", "triggerable", "currency", "firstTradeDateMilliseconds", "postMarketChangePercent", "postMarketTime", "postMarketPrice", "postMarketChange", "regularMarketChange", "regularMarketChangePercent", "regularMarketTime", "regularMarketPrice", "regularMarketDayHigh", "regularMarketDayRange", "regularMarketDayLow", "regularMarketVolume", "regularMarketPreviousClose", "bid", "ask", "bidSize", "askSize", "fullExchangeName", "financialCurrency", "regularMarketOpen", "averageDailyVolume3Month", "averageDailyVolume10Day", "fiftyTwoWeekLowChange", "fiftyTwoWeekLowChangePercent", "fiftyTwoWeekRange", "fiftyTwoWeekHighChange", "fiftyTwoWeekHighChangePercent", "fiftyTwoWeekLow", "fiftyTwoWeekHigh", "dividendDate", "marketState", "earningsTimestamp", "earningsTimestampStart", "earningsTimestampEnd", "trailingAnnualDividendRate", "trailingPE", "trailingAnnualDividendYield", "epsTrailingTwelveMonths", "epsForward", "epsCurrentYear", "priceEpsCurrentYear", "sharesOutstanding", "bookValue", "fiftyDayAverage", "fiftyDayAverageChange", "fiftyDayAverageChangePercent", "twoHundredDayAverage", "twoHundredDayAverageChange", "twoHundredDayAverageChangePercent", "marketCap", "forwardPE", "priceToBook", "sourceInterval", "exchangeDataDelayedBy", "pageViewGrowthWeekly", "averageAnalystRating", "tradeable", "priceHint", "exchange", "shortName", "longName", "messageBoardId", "exchangeTimezoneName", "exchangeTimezoneShortName", "gmtOffSetMilliseconds", "market", "esgPopulated", "displayName", "symbol"}, {"language", "region", "quoteType", "quoteSourceName", "triggerable", "currency", "firstTradeDateMilliseconds", "postMarketChangePercent", "postMarketTime", "postMarketPrice", "postMarketChange", "regularMarketChange", "regularMarketChangePercent", "regularMarketTime", "regularMarketPrice", "regularMarketDayHigh", "regularMarketDayRange", "regularMarketDayLow", "regularMarketVolume", "regularMarketPreviousClose", "bid", "ask", "bidSize", "askSize", "fullExchangeName", "financialCurrency", "regularMarketOpen", "averageDailyVolume3Month", "averageDailyVolume10Day", "fiftyTwoWeekLowChange", "fiftyTwoWeekLowChangePercent", "fiftyTwoWeekRange", "fiftyTwoWeekHighChange", "fiftyTwoWeekHighChangePercent", "fiftyTwoWeekLow", "fiftyTwoWeekHigh", "dividendDate", "marketState", "earningsTimestamp", "earningsTimestampStart", "earningsTimestampEnd", "trailingAnnualDividendRate", "trailingPE", "trailingAnnualDividendYield", "epsTrailingTwelveMonths", "epsForward", "epsCurrentYear", "priceEpsCurrentYear", "sharesOutstanding", "bookValue", "fiftyDayAverage", "fiftyDayAverageChange", "fiftyDayAverageChangePercent", "twoHundredDayAverage", "twoHundredDayAverageChange", "twoHundredDayAverageChangePercent", "marketCap", "forwardPE", "priceToBook", "sourceInterval", "exchangeDataDelayedBy", "pageViewGrowthWeekly", "averageAnalystRating", "tradeable", "priceHint", "exchange", "shortName", "longName", "messageBoardId", "exchangeTimezoneName", "exchangeTimezoneShortName", "gmtOffSetMilliseconds", "market", "esgPopulated", "displayName", "symbol"})
in
    #"Expanded quoteResponse.result1"

Step 2 – Make the Connection String Dynamic

Now that we converted the query to a function and required a stock symbol be passed, it is time for us to use it dynamically with the connection string.

On line 3, you see our endpoint url and parameters that specify MSFT as our designated stock. We need to replace MSFT with our new variable. We will simply delete the current symbol, add an ampersand (&) outside of the quotes, and specify StockSymbol to concatenate the values together.

(StockSymbol as text)=>
let
    Source = Json.Document(Web.Contents("https://yfapi.net/v6/finance/quote?region=US&lang=en&symbols=" & StockSymbol, [Headers=[Accept="application/json", #"X-API-KEY"="bHjSKbiRtr4cOzmOopdrI5TUKX5kMKXU6weUteeL"]])),
    #"Converted to Table" = Table.FromRecords({Source}),
    #"Expanded quoteResponse" = Table.ExpandRecordColumn(#"Converted to Table", "quoteResponse", {"result", "error"}, {"quoteResponse.result", "quoteResponse.error"}),
    #"Expanded quoteResponse.result" = Table.ExpandListColumn(#"Expanded quoteResponse", "quoteResponse.result"),
    #"Expanded quoteResponse.result1" = Table.ExpandRecordColumn(#"Expanded quoteResponse.result", "quoteResponse.result", {"language", "region", "quoteType", "quoteSourceName", "triggerable", "currency", "firstTradeDateMilliseconds", "postMarketChangePercent", "postMarketTime", "postMarketPrice", "postMarketChange", "regularMarketChange", "regularMarketChangePercent", "regularMarketTime", "regularMarketPrice", "regularMarketDayHigh", "regularMarketDayRange", "regularMarketDayLow", "regularMarketVolume", "regularMarketPreviousClose", "bid", "ask", "bidSize", "askSize", "fullExchangeName", "financialCurrency", "regularMarketOpen", "averageDailyVolume3Month", "averageDailyVolume10Day", "fiftyTwoWeekLowChange", "fiftyTwoWeekLowChangePercent", "fiftyTwoWeekRange", "fiftyTwoWeekHighChange", "fiftyTwoWeekHighChangePercent", "fiftyTwoWeekLow", "fiftyTwoWeekHigh", "dividendDate", "marketState", "earningsTimestamp", "earningsTimestampStart", "earningsTimestampEnd", "trailingAnnualDividendRate", "trailingPE", "trailingAnnualDividendYield", "epsTrailingTwelveMonths", "epsForward", "epsCurrentYear", "priceEpsCurrentYear", "sharesOutstanding", "bookValue", "fiftyDayAverage", "fiftyDayAverageChange", "fiftyDayAverageChangePercent", "twoHundredDayAverage", "twoHundredDayAverageChange", "twoHundredDayAverageChangePercent", "marketCap", "forwardPE", "priceToBook", "sourceInterval", "exchangeDataDelayedBy", "pageViewGrowthWeekly", "averageAnalystRating", "tradeable", "priceHint", "exchange", "shortName", "longName", "messageBoardId", "exchangeTimezoneName", "exchangeTimezoneShortName", "gmtOffSetMilliseconds", "market", "esgPopulated", "displayName", "symbol"}, {"language", "region", "quoteType", "quoteSourceName", "triggerable", "currency", "firstTradeDateMilliseconds", "postMarketChangePercent", "postMarketTime", "postMarketPrice", "postMarketChange", "regularMarketChange", "regularMarketChangePercent", "regularMarketTime", "regularMarketPrice", "regularMarketDayHigh", "regularMarketDayRange", "regularMarketDayLow", "regularMarketVolume", "regularMarketPreviousClose", "bid", "ask", "bidSize", "askSize", "fullExchangeName", "financialCurrency", "regularMarketOpen", "averageDailyVolume3Month", "averageDailyVolume10Day", "fiftyTwoWeekLowChange", "fiftyTwoWeekLowChangePercent", "fiftyTwoWeekRange", "fiftyTwoWeekHighChange", "fiftyTwoWeekHighChangePercent", "fiftyTwoWeekLow", "fiftyTwoWeekHigh", "dividendDate", "marketState", "earningsTimestamp", "earningsTimestampStart", "earningsTimestampEnd", "trailingAnnualDividendRate", "trailingPE", "trailingAnnualDividendYield", "epsTrailingTwelveMonths", "epsForward", "epsCurrentYear", "priceEpsCurrentYear", "sharesOutstanding", "bookValue", "fiftyDayAverage", "fiftyDayAverageChange", "fiftyDayAverageChangePercent", "twoHundredDayAverage", "twoHundredDayAverageChange", "twoHundredDayAverageChangePercent", "marketCap", "forwardPE", "priceToBook", "sourceInterval", "exchangeDataDelayedBy", "pageViewGrowthWeekly", "averageAnalystRating", "tradeable", "priceHint", "exchange", "shortName", "longName", "messageBoardId", "exchangeTimezoneName", "exchangeTimezoneShortName", "gmtOffSetMilliseconds", "market", "esgPopulated", "displayName", "symbol"})
in
    #"Expanded quoteResponse.result1"

With the dynamic variable placed in the connection string, the function will now pull stock quote information based upon the symbols we have in our list.

Step 3 – Resolve Connection String Errors

If you clicked done and used this function in your report, everything would be fine inside of Power BI Desktop. However, you are going to run into an issue when you publish to the Power BI Service. Because we are using a dynamic parameter, the cloud service is not sure how to handle authentication. The result is an error in the Power BI Service and a report that will not refresh.’

To resolve this issue, we want to split our connection string to resolve this issue. We will essentially split it into two different sections. The first section will serve as the connection string and help with our authentication. The second half will be treated as a dynamic portion of the query and has no bearing on the authentication process.

In short, all we are going to do is move a portion of that connection string to prevent any errors from happening.

(StockSymbol as text)=>
let
    Source = Json.Document(Web.Contents("https://yfapi.net/v6/finance/", [RelativePath="quote?region=US&lang=en&symbols=" & StockSymbol, Headers=[Accept="application/json", #"X-API-KEY"="bHjSKbiRtr4cOzmOopdrI5TUKX5kMKXU6weUteeL"]])),
    #"Converted to Table" = Table.FromRecords({Source}),
    #"Expanded quoteResponse" = Table.ExpandRecordColumn(#"Converted to Table", "quoteResponse", {"result", "error"}, {"quoteResponse.result", "quoteResponse.error"}),
    #"Expanded quoteResponse.result" = Table.ExpandListColumn(#"Expanded quoteResponse", "quoteResponse.result"),
    #"Expanded quoteResponse.result1" = Table.ExpandRecordColumn(#"Expanded quoteResponse.result", "quoteResponse.result", {"language", "region", "quoteType", "quoteSourceName", "triggerable", "currency", "firstTradeDateMilliseconds", "postMarketChangePercent", "postMarketTime", "postMarketPrice", "postMarketChange", "regularMarketChange", "regularMarketChangePercent", "regularMarketTime", "regularMarketPrice", "regularMarketDayHigh", "regularMarketDayRange", "regularMarketDayLow", "regularMarketVolume", "regularMarketPreviousClose", "bid", "ask", "bidSize", "askSize", "fullExchangeName", "financialCurrency", "regularMarketOpen", "averageDailyVolume3Month", "averageDailyVolume10Day", "fiftyTwoWeekLowChange", "fiftyTwoWeekLowChangePercent", "fiftyTwoWeekRange", "fiftyTwoWeekHighChange", "fiftyTwoWeekHighChangePercent", "fiftyTwoWeekLow", "fiftyTwoWeekHigh", "dividendDate", "marketState", "earningsTimestamp", "earningsTimestampStart", "earningsTimestampEnd", "trailingAnnualDividendRate", "trailingPE", "trailingAnnualDividendYield", "epsTrailingTwelveMonths", "epsForward", "epsCurrentYear", "priceEpsCurrentYear", "sharesOutstanding", "bookValue", "fiftyDayAverage", "fiftyDayAverageChange", "fiftyDayAverageChangePercent", "twoHundredDayAverage", "twoHundredDayAverageChange", "twoHundredDayAverageChangePercent", "marketCap", "forwardPE", "priceToBook", "sourceInterval", "exchangeDataDelayedBy", "pageViewGrowthWeekly", "averageAnalystRating", "tradeable", "priceHint", "exchange", "shortName", "longName", "messageBoardId", "exchangeTimezoneName", "exchangeTimezoneShortName", "gmtOffSetMilliseconds", "market", "esgPopulated", "displayName", "symbol"}, {"language", "region", "quoteType", "quoteSourceName", "triggerable", "currency", "firstTradeDateMilliseconds", "postMarketChangePercent", "postMarketTime", "postMarketPrice", "postMarketChange", "regularMarketChange", "regularMarketChangePercent", "regularMarketTime", "regularMarketPrice", "regularMarketDayHigh", "regularMarketDayRange", "regularMarketDayLow", "regularMarketVolume", "regularMarketPreviousClose", "bid", "ask", "bidSize", "askSize", "fullExchangeName", "financialCurrency", "regularMarketOpen", "averageDailyVolume3Month", "averageDailyVolume10Day", "fiftyTwoWeekLowChange", "fiftyTwoWeekLowChangePercent", "fiftyTwoWeekRange", "fiftyTwoWeekHighChange", "fiftyTwoWeekHighChangePercent", "fiftyTwoWeekLow", "fiftyTwoWeekHigh", "dividendDate", "marketState", "earningsTimestamp", "earningsTimestampStart", "earningsTimestampEnd", "trailingAnnualDividendRate", "trailingPE", "trailingAnnualDividendYield", "epsTrailingTwelveMonths", "epsForward", "epsCurrentYear", "priceEpsCurrentYear", "sharesOutstanding", "bookValue", "fiftyDayAverage", "fiftyDayAverageChange", "fiftyDayAverageChangePercent", "twoHundredDayAverage", "twoHundredDayAverageChange", "twoHundredDayAverageChangePercent", "marketCap", "forwardPE", "priceToBook", "sourceInterval", "exchangeDataDelayedBy", "pageViewGrowthWeekly", "averageAnalystRating", "tradeable", "priceHint", "exchange", "shortName", "longName", "messageBoardId", "exchangeTimezoneName", "exchangeTimezoneShortName", "gmtOffSetMilliseconds", "market", "esgPopulated", "displayName", "symbol"})
in
    #"Expanded quoteResponse.result1"

This is a subtle but important move. By placing the dynamic content inside the relative path, we have eliminated confusion during the authentication process. Power BI will authenticate on the connection string in place and then concatenate the relative path to complete the request. You only need to do this when your queries are dynamic, but it is an important step in the process!

Step 4 – Test the Function

After clicking done, your view in Power Query will have changed. Note that the query list now shows an “fx” symbol for function and you no longer have a tabular list of data. In fact, you have a place to enter a stock symbol. This allows us to quickly test to see if our function is working. Go ahead and enter MSFT and click invoke to see it work.

The outcome from converting a query to a function.

After you invoke the function, you will be taken to a new query called “Invoked Function” and can see the stock data for Microsoft. That was just to test and see if it works. We can go ahead and delete that query because will not need it again.

Applying the Function

With a working function in hand, it is time to apply it to our data model. I already have a list of stocks in Excel, so I will import that list to Power BI. If your stock list is somewhere else, that is perfectly fine. Just get that list of symbols inside of Power BI.

Once imported, go to your “Add Column” tab and select “Invoke Custom Function”. Select your function from the list. You will notice that the StockSymbol variable will appear. We need to specify which column of values will be passed to that variable. Select the symbol column and click OK.

Setting up the custom function to use the stock symbols from the spreadsheet.

At this point, you may be prompted about data privacy. If you do, just select ignore privacy levels for now as this is a demonstration.

You should have a new column with the word table highlighted inside of it. We can go ahead and expand those values and confirm the current stock quote info is available for each different symbol.

And just like that you created your own function and applied it in Power Query!

Next Steps

With a working function, what is left to discuss? As I stated at the beginning of this series – each API is like a snowflake. They all are unique and have their own challenges. This is a single example of how to connect a REST API to Power BI. There are challenges and other considerations you must balance when creating your reports. In my next post, I will dive into challenges you will see when leveraging other APIs inside of Power BI.

Until then, was this your first time creating a function? Did you find it challenging? Do you have other applications for a function inside of Power BI? Tell me in the comments below!

How do I connect to REST APIs with Power BI? (Part 2)

In my last article, I discussed the basics around REST APIs. We did not do anything with Power BI. While I did not get to the real purpose of this series, it was important to establish the basics. Using the elements we discussed, you can copy and paste them into Power BI to connect to your API.

For this post, I am using the Yahoo Finance API to demonstrate how this works. I do not receive any kick back for sharing this API. They have a free tier and it is a great API to use for beginners. If you want to try this on your own, you can follow along with this article.

RTFM – Read the Fine Manual

Before you do anything with APIs you must review the documentation. I worked with an API a few years ago that required a unique API key for each individual endpoint. If I had spent some time reviewing the documentation, I would have prevented hours of frustration as I tried to connect to various endpoints. If you are like me, you might find this frustrating. However, I find reading the documentation far less frustrating than constantly struggling to get an endpoint to work inside of Power BI.

Identifying Usable Endpoints

The first time I review documentation I am sorting out which endpoints are useable or not. I need to find out which API endpoints that will work with Power BI. I opened the documentation and was excited about what I saw…

Yahoo Finance API Documentation – Welcome Page

At a quick glace, all of the endpoints are using the GET connection method. As a result that means every single endpoint can be used in Power BI. That is great to see!

Which Endpoint is for Me?

Knowing what you are looking for is key when reviewing documentation. I want to find stock price information and compare prices. Quality documentation will have one of two things to help you with this process:

  • A data dictionary that clearly defines each column in the query
  • A same response from the query
Sample Response from the Quote Endpoint

This documentation brings back a sample. As expected, I can find which endpoint meets my needs. As an example, I am interested in stock prices. According to the documentation, I can use the quote endpoint to accomplish my goal.

Setting the Parameters

Now that we have the connection method and endpoint, we need to establish what parameters are needed. Sometimes parameters are identified but not clearly documented. As a result, you might get frustrated because you cannot apply parameters to your query. This API is much easier to understand because of the clearly labeled parameters with sample inputs.

Sample Parameters for the Quote Endpoint

As you can see, the documentation provides sample values for each parameter so you know how to leverage it. This results in far less frustration because you know what values are accepted for each parameter.

Since this API has an option to try it out, I have updated the parameters to pull back Microsoft’s stock information. This allowed me to validate if my parameters were correct and saved me a ton of headaches. Since it worked, you can see that it came back with a response.

Testing Parameters for the Quote Endpoint

As a result of the test, there was another valuable bonus. The documentation assembled my connection string by putting the endpoint url and parameters together into a single connection string. How convenient!

The Connection String is Assembled Automatically

Header Information

Up to this point the Yahoo Finance API documentation has explicitly called out each of the REST API elements. However, the headers in this endpoint are not as easily identified. We have to dig a little deeper to find them.

You probably missed the box above the connection string labeled Curl. Curl is a library used to call APIs if you are developing an application. While we are not developing an application, this box does help us identify our headers. If you notice, there are two lines with “-H” at the beginning. These are the header values we have been looking for!

You have to look closely, but here are the header values we need for the quote endpoint!

Double Check

At this stage, we have identified all the elements we need for our connection in Power BI. We have the connection method, endpoint url, parameters, and headers. Remember that we are not using a POST connection method, so we do not need a body for this request. Now we just need to plug it into Power BI!

Connecting to our REST API (Finally!)

With all of our elements in hand, we can open up Power BI and start building our connection to the API.

Step 1 – Select the Web Connector

Open Power BI, select “Get Data”, and choose the Web connector:

Step 2 – Copy/Paste Elements into Power BI

First things first – switch to the advanced mode for the Web Connector so you can add all of the required details.

Using the Advanced Web Connector view in Power BI

You will notice that there is no option to select your connection method in this view. Power BI defaults this to GET, so you do not need to configure anything. One less thing to worry about!

Go back to the documentation and copy your connection string. If your documentation does not assemble this for you, you might need to do this by hand. Once assembled, paste it into the URL Parts box in the web connector.

Highlighting where the Connection String (endpoint url and parameters) goes in the Advanced View of the Web Connector for Power BI

Next, we can add the headers. This endpoint has two – Accept and X-API-KEY. You will only see one set of boxes at the bottom. You can simply click “Add header” to accommodate for the second header.

For the Accept header, we can select it from the drop down on the left as it is a commonly used header. On the right, copy and paste the value found in the API documentation.

For the X-API-KEY header, you can enter your own value in the drop down or copy/paste it from your documentation. You can copy your API key from the documentation as well into the box on the right.

Highlighting where the headers go in the Advanced View of the Web Connector for Power BI

By bridging the documentation with the web connector, we have successfully filled in all of the blanks for the connection method, endpoint url, parameters, and headers. With everything in place, we can click okay and connect!

Step 3 – Authentication

Since this is the first time we are using this connection, we are prompted to select an authentication method. If you are using the Yahoo Finance API, you can easily select Anonymous and click connect as our API key handles our authentication.

Authenticating with the Power BI Web Connector

On the left, you can see there are other authentication methods. Sometimes your connection method will require a username and password. I have experienced this with ServiceNow and Honeywell APIs. If you are adventurous and want to try using the Microsoft Graph API, you will need to use your organizational account. You will need to review your documentation to determine if you need to use other forms of authentication to use an endpoint.

Transformation Time

By following these steps, you should now see your stock data inside of Power Query. All of the information you saw in the sample response should be in a tabular format and ready to go. For an easier view, I made some adjustments so you could easily compare the values:

Data from the quote endpoint

With this query in place, you can quickly refresh the data and will always have up to date stock quote information for Microsoft.

Next Steps

With everything connected, you probably think we are finished. If there was only one stock we cared about, we would be in good shape. However, investors are looking at several different stocks each and every day and this query only pulled one. In my next article, I will show you how to scale this query so whether you have 10 or 1,000, you will have a maintenance free report that always refreshes.

Until then, did you follow along with the Yahoo Finance API? Were you able to get some valid data? Tell me in the comments below!

How do I connect to REST APIs with Power BI? (Part 1)

We have all been there once before. We are using a 3rd party tool and we want to extract data from it. You might have an option to export the data to a CSV or Excel file, but that requires you to be involved and can result in errors.

Next you might reach out to the support team and they send back documentation for their APIs. They think you are a programmer and probably know how to use them. But in a world full of citizen developers not everyone knows how to use these APIs to bring data into Power BI.

This series aims to demystify the process and start getting that data into Power BI and eliminate those data exports. And believe it or not, it will require a minimal amount of code to make it happen!

What is REST?

REST APIs (also known as RESTful APIs) is a specific architecture of an Application Program Interface (API). Known as the Representational State Transfer (REST), it has a standard set of rules that are widely used in the programming world. It scales well and has become a commonly accepted API that is stable. REST APIs fill a specific need to simplify integration of data between different applications.

API requests are fairly straight forward. It starts with a request from you (the client) to an API endpoint. After being authenticated, the request continues on to a database with the data you need. That data is returned back to the endpoint where it is formatted and then passed back to the client.

The flow of requests with REST APIs

For many, this flow makes a ton of sense. However, if you are not from a programming background, this can be a little intimidating. When you read API documentation, it is often full of code snippets and it can be difficult for you to get started. Sorting through these snippets and figuring out how they work can be complicated and time consuming.

But here is the good news – Power BI can handle these API requests often with no code required! The trick is understanding how REST APIs are structured and then plugging the information from the documentation into the right place.

Anatomy of REST APIs

So if we can take the documentation and plug it into Power BI, it is important to understand how REST APIs are constructed. Once we understand the anatomy of an API, it becomes easier to connect via Power BI. There are five common elements that are leveraged with REST APIs:

  • Method
  • Endpoint URL
  • Parameters
  • Headers
  • Body

You will always need a Method and Endpoint URL when using REST. However, the other elements will vary based off of the documentation. You will have to read your documentation carefully to make sure you have everything you need.

To better understand these elements, let’s take a look at them individually to understand what they do and how they work.

Method

There are four methods that are used with REST APIs: Get, Post, Put, and Delete. Methods help instruct what is happening when the API is called. For example, if you called a Delete method, you would be deleting content on the server.

Sample methods used with REST APIs

To make our lives easier, the Get method is the most common one we will use and is the easiest to implement. Post can be used sometimes, but it requires a little more effort and coding in Power Query to make it work. You will never use Put or Delete in Power BI.

Endpoint URL

The Endpoint URL determines which specific API we are calling. There will be multiple APIs available, so it is important to determine which one you need. In reviewing your documentation, you should be able to determine which endpoint contains the data you need to query.

Sample of an Endpoint URL

Parameters

Parameters filter the results of your request. Notice the “?” that separates the connection string. Before it is the Endpoint URL while after are the parameters. Multiple parameters are separated by ampersands (&) and strung together.

Sample of Parameters tacked on to an Endpoint URL

Parameters speed up requests as they reduce the amount of data requested. Often optional, but sometimes required. The API documentation will specify if any parameters are required.

Headers

Headers are designed to pass information about your request. The most common header you will need is an API key or token but you may run into others. Another common header you might need to identify is the Content-Type to specify how we want to see our response.

Sample of REST API Headers

Body

Since we are using the Get method primarily in Power BI, you will not need the body. However, it is worth mentioning it in case you use a Post method in the future. The body is another set of parameters to assist with the request.

Documentation is your friend

If you are like me then you hate reading directions. I love working through things on my own and discovering. But after many lost hours, I have learned to take a few minutes to read the documentation that comes with an API first. It takes time, but there are several reasons to take a few minutes and understand how the API functions.

Sample of REST API documentation for Yahoo! Finance

Reason #1 – Each API is like a snowflake

Every API is unique and has their own requirements. You will find some that require certain parameters to be declared. Others might need a token. I even worked with one that required a different API key for each endpoint! Understanding the components of our API will reduce headaches as we go to use them.

Reason #2 – Ensure you are receiving the right data

The best documentation is paired with a dictionary to help identify the data being returned. You may need two or three APIs to get all the information you need. Taking a few minutes to understand what data is available will help you build a plan as you build your connections.

Reason #3 – Try it out

Often APIs have an option to try them out. You can select an endpoint, fill out the parameters, and then submit the request. It brings back a sample of the data and even will assemble the connection string and headers for you. It also helps identify any required parameters and diagnose issues before you move into Power BI.

Next Steps

Now that we understand the basics around REST APIs, the next step is to transfer what we have learned into Power BI. You might be wondering why I took the time to understand the basics of REST APIs. As I mentioned before, many of these APIs can be used with no code. With a strong understanding of how they are built, we can copy/paste these elements into Power BI and start querying data. In my next post, I will show you how to connect to the Yahoo Finance API to query stock quotes in real time.

Until then, have you used any APIs in Power BI? Do you have any favorites that you like to use? Tell me in the comments below!

Page 4 of 5

Powered by WordPress & Theme by Anders Norén