2

I have data as:

s = '{"Date":{"0":"2016-10-03 00:00:00","1":"2016-10-03 00:00:00","2":"2016-10-03 00:00:00","3":"2016-10-04 00:00:00","4":"2016-10-04 00:00:00","5":"2016-10-04 00:00:00","6":"2016-10-05 00:00:00","7":"2016-10-05 00:00:00","8":"2016-10-05 00:00:00"},"Close":{"0":31.5,"1":112.52,"2":57.42,"3":113.0,"4":57.24,"5":31.35,"6":57.64,"7":31.59,"8":113.05},"Volume":{"0":14070500,"1":21701800,"2":19189500,"3":29736800,"4":20085900,"5":18460400,"6":16726400,"7":11808600,"8":21453100},"Symbol":{"0":"CSCO","1":"AAPL","2":"MSFT","3":"AAPL","4":"MSFT","5":"CSCO","6":"MSFT","7":"CSCO","8":"AAPL"}}'
df = pd.read_json(s)

Which looks like:

        Date  Close    Volume Symbol
0 2016-10-03  31.50  14070500   CSCO
1 2016-10-03 112.52  21701800   AAPL
2 2016-10-03  57.42  19189500   MSFT
3 2016-10-04 113.00  29736800   AAPL
4 2016-10-04  57.24  20085900   MSFT
5 2016-10-04  31.35  18460400   CSCO
6 2016-10-05  57.64  16726400   MSFT
7 2016-10-05  31.59  11808600   CSCO
8 2016-10-05 113.05  21453100   AAPL

I can create the styling required with the following:

format_dict = dict(Date="{:%m/%d/%y}", Close="${:.2f}", Volume="{:,}")
(
    df.style.format(format_dict)
    .hide_index()
    .bar("Volume", color="lightblue", align="zero")
)

Which looks as:

enter image description here

But when I write to an excel file using:

format_dict = dict(Date="{:%m/%d/%y}", Close="${:.2f}", Volume="{:,}")
df_formatted = (
    df.style.format(format_dict)
    .hide_index()
    .bar("Volume", color="lightblue", align="zero")
)
df_formatted.to_excel("demo.xlsx")

It gives me the following:

enter image description here

I'm not sure how to fix this.

Here are the packages that I have installed for the virtualenv creating this example:

-> % pip freeze
et-xmlfile==1.0.1
jdcal==1.4.1
Jinja2==2.11.1
MarkupSafe==1.1.1
numpy==1.18.2
openpyxl==3.0.3
pandas==1.0.3
python-dateutil==2.8.1
pytz==2019.3
six==1.14.0
3
  • I don't think it is possible to have a bar chart with the value over it. Are you OK with a separate column for the bar charts? Commented Sep 30, 2020 at 23:31
  • @jakub if that's a limitation of pandas styling and is explained then fair enough - i found it kinda tricky to research anything definite on this, so it'd be handy if this post could serve as that for others in future. Commented Sep 30, 2020 at 23:33
  • 1
    Sorry, it is possible. please see my answer below stackoverflow.com/a/64147995/5666087 Commented Oct 1, 2020 at 2:32

2 Answers 2

6
+100

In Excel, the in-cell bar plot is called a data bar, and you can add it with conditional formatting. I have demonstrated how to do this with openpyxl and xlsxwriter. I would suggest using xlsxwriter because it allows you to choose gradient or solid background, whereas openpyxl does not have this option and produces a data bar with a gradient.

XlsxWriter

import pandas as pd
from xlsxwriter.utility import xl_range

s = '{"Date":{"0":"2016-10-03 00:00:00","1":"2016-10-03 00:00:00","2":"2016-10-03 00:00:00","3":"2016-10-04 00:00:00","4":"2016-10-04 00:00:00","5":"2016-10-04 00:00:00","6":"2016-10-05 00:00:00","7":"2016-10-05 00:00:00","8":"2016-10-05 00:00:00"},"Close":{"0":31.5,"1":112.52,"2":57.42,"3":113.0,"4":57.24,"5":31.35,"6":57.64,"7":31.59,"8":113.05},"Volume":{"0":14070500,"1":21701800,"2":19189500,"3":29736800,"4":20085900,"5":18460400,"6":16726400,"7":11808600,"8":21453100},"Symbol":{"0":"CSCO","1":"AAPL","2":"MSFT","3":"AAPL","4":"MSFT","5":"CSCO","6":"MSFT","7":"CSCO","8":"AAPL"}}'
df = pd.read_json(s)

def get_range(df, column_name):
    """Return coordinates for a column range given a column name.

    For example, if "Volume" is the third column and has 10 items,
    output is "C2:C10".
    """
    col = df.columns.get_loc(column_name)
    rows = df.shape[0]
    # Use 1 to skip the header.
    return xl_range(1, col, rows, col)

writer = pd.ExcelWriter("output.xlsx", engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1", index=False)
worksheet = writer.sheets["Sheet1"]
range_ = get_range(df, "Volume")
worksheet.conditional_format(range_, {'type': 'data_bar', 'bar_solid': True})
writer.save()

Sample output:

Output of databars with XlsxWriter

Openpyxl (does not support solid data bars)

from openpyxl.formatting.rule import DataBar, FormatObject, Rule
import pandas as pd

s = '{"Date":{"0":"2016-10-03 00:00:00","1":"2016-10-03 00:00:00","2":"2016-10-03 00:00:00","3":"2016-10-04 00:00:00","4":"2016-10-04 00:00:00","5":"2016-10-04 00:00:00","6":"2016-10-05 00:00:00","7":"2016-10-05 00:00:00","8":"2016-10-05 00:00:00"},"Close":{"0":31.5,"1":112.52,"2":57.42,"3":113.0,"4":57.24,"5":31.35,"6":57.64,"7":31.59,"8":113.05},"Volume":{"0":14070500,"1":21701800,"2":19189500,"3":29736800,"4":20085900,"5":18460400,"6":16726400,"7":11808600,"8":21453100},"Symbol":{"0":"CSCO","1":"AAPL","2":"MSFT","3":"AAPL","4":"MSFT","5":"CSCO","6":"MSFT","7":"CSCO","8":"AAPL"}}'
df = pd.read_json(s)

first = FormatObject(type='min')
second = FormatObject(type='max')
data_bar = DataBar(cfvo=[first, second], color="ADD8E6", showValue=None, minLength=None, maxLength=None)
rule = Rule(type='dataBar', dataBar=data_bar)

writer = pd.ExcelWriter("output.xlsx", engine="openpyxl")
df.to_excel(writer, sheet_name="Sheet1", index=False)
worksheet = writer.sheets['Sheet1']

# Add data bar to Volume column.
start = worksheet["C"][1].coordinate
end = worksheet["C"][-1].coordinate
worksheet.conditional_formatting.add(f"{start}:{end}", rule)

writer.save()
writer.close()

Sample output:

Output of databars with openpyxl

REPT function

Another option is to create in-cell bar plots is to use the REPT function in Excel. It's not as pretty as the data bar :)

import pandas as pd
s = '{"Date":{"0":"2016-10-03 00:00:00","1":"2016-10-03 00:00:00","2":"2016-10-03 00:00:00","3":"2016-10-04 00:00:00","4":"2016-10-04 00:00:00","5":"2016-10-04 00:00:00","6":"2016-10-05 00:00:00","7":"2016-10-05 00:00:00","8":"2016-10-05 00:00:00"},"Close":{"0":31.5,"1":112.52,"2":57.42,"3":113.0,"4":57.24,"5":31.35,"6":57.64,"7":31.59,"8":113.05},"Volume":{"0":14070500,"1":21701800,"2":19189500,"3":29736800,"4":20085900,"5":18460400,"6":16726400,"7":11808600,"8":21453100},"Symbol":{"0":"CSCO","1":"AAPL","2":"MSFT","3":"AAPL","4":"MSFT","5":"CSCO","6":"MSFT","7":"CSCO","8":"AAPL"}}'
df = pd.read_json(s)

writer = pd.ExcelWriter("output.xlsx", engine="openpyxl")
df.to_excel(writer, sheet_name="Sheet1", index=False)
worksheet = writer.sheets['Sheet1']

# Use column E because that is the next empty column.
for row, cell in enumerate(worksheet["E"]):
    # Add 1 because Python's indexing starts at 0 and Excel's does not.
    row += 1
    if row != 1:
        # Column C corresponds to Volume.
        value = f'=REPT("|", C{row} / 1000000)'
    else:
        value = "Bar"
    worksheet[f"E{row}"] = value

writer.save()
writer.close()

Sample output:

Output of databars with rept function

Sign up to request clarification or add additional context in comments.

3 Comments

i think REPT has a certain charm of it's own :) To be clear, it's not (currently) possible to produce this output using pandas style - therefore the above are necessary? If I'm understanding correctly, then all good, I'd consider the above a solution to the problem. Perhaps some are interested in the output - it's here (the order of the output there is repl / openpyxl / xlsxwriter ) imgur.com/a/iZdgYN8
Yes, it seems like exporting in-cell bar charts to Excel is not supported yet with the pandas style API. But in the pandas user guide, there is a note that features are being added, so perhaps data bars are on their list.
hope so! until then I think this is a good alternative, thank you :)
1

You just do the format which is for the display purpose, we should assign the columns

df.Volume= df.Volume.map(lambda x: "{:,}".format(x))
df#df.to_excel("demo.xlsx")

         Date   Close      Volume Symbol
0  2016-10-03   31.50  14,070,500   CSCO
1  2016-10-03  112.52  21,701,800   AAPL
2  2016-10-03   57.42  19,189,500   MSFT
3  2016-10-04  113.00  29,736,800   AAPL
4  2016-10-04   57.24  20,085,900   MSFT
5  2016-10-04   31.35  18,460,400   CSCO
6  2016-10-05   57.64  16,726,400   MSFT
7  2016-10-05   31.59  11,808,600   CSCO
8  2016-10-05  113.05  21,453,100   AAPL

1 Comment

thanks, that makes sense... I'm particularly interested in the bar charts within the column though

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.