How to create word documents (.docx) using Python

This blog will show you how to create word documents (.docx) using Python. This can be very useful, especially in the generation of reports. I’ll be using below report as an example.

The library that will be used to do this in this blog is docx v0.8.11.

In [1]:

# !pip install python-docx=0.8.11

Libraries:

In [2]:

import pandas as pd, numpy as np
import matplotlib.pyplot as plt

# docx
from docx import Document
from docx.shared import Inches

In [3]:

import warnings
warnings.filterwarnings("ignore")

Using Python, we can create MS Word documents (.docx) containing headings, paragraphs, tables, pictures, amongst others. Below script shows some of the mainstream functionalities that one might want to do in a MS Word document.

More info can be found on the documentation https://python-docx.readthedocs.io/en/latest/.

In [4]:

# the document is assigned to 'document'
document = Document()

# add headings
document.add_heading('Heading 0', level=0)
document.add_heading('Heading 1', level=1)
document.add_heading('Heading 2', level=2)
# f-strings can be used as well
heading_size = 3
document.add_heading(f'Heading {heading_size}', heading_size)  

# doing an empty '.add_paragraph' will act as a newline
p = document.add_paragraph()

# otherwise, texts can be entered in the brackets. note that each paragraph is assigned to a different name. 
# it helps in differentiating and customising them
para_0 = document.add_paragraph('This is how we input paragraphs in the document. This paragraph has been declared as 
variable para_0.')

# adding text and setting some of the text to bold and italics by using '.add_run'
para_1 = document.add_paragraph('This is another paragraph para_1. By using the variables that they are assigned to, 
we can manipulate the texts such as ')
para_1.add_run('setting text to bold, ').bold = True
para_1.add_run('or if you prefer italics. ').italic = True
para_1.add_run('Notice that they all form part of the same paragraph and are on the same line.')

# adding an unordered list
document.add_paragraph('First item in unordered list.', style='List Bullet')
document.add_paragraph('Second item in unordered list.', style='List Bullet')

# adding an ordered list
document.add_paragraph('First item in ordered list.', style='List Number')
document.add_paragraph('Second item in ordered list.', style='List Number')

# adding a picture and customising the size/width
# note: the picture must be saved locally
document.add_picture('maths.jpg', width=Inches(2.5))

# adding a table using a tuple of tuples
# can be a dataframe as well instead of a tuple - needs to be iterable
# first describe the data
records = (
            ('FR0101', 'French', 73),
            ('MA1019', 'Mathematics', 97),
            ('EN0631', 'English', 84)
    )

# create a table with 3 columns
# only 1 row has been created for the header
# add we iterate through the data, we will add rows accordingly
table = document.add_table(rows=1, cols=3)
# naming the header cells
header_cells = table.rows[0].cells
header_cells[0].text = 'Subject Code'
header_cells[1].text = 'Subject'
header_cells[2].text = 'Grades'
# iterate through the data
# add a new row
# assign data points to the cells as required
for subject_code, subject, grade in records:
    row_cells = table.add_row().cells
    row_cells[0].text = subject_code
    row_cells[1].text = subject
    row_cells[2].text = str(grade)

# save as a .docx document
document.save('testing_basic_document.docx')

The above script shows how to do some of the main functionalities of the python-docx library.

Now let’s get a dataset and do something more fun (and probably more useful).

Getting the raw data: (Dataset obtained from Kaggle. Link: https://www.kaggle.com/datasets/rohitsahoo/sales-forecasting)

In [5]:

raw_data = pd.read_csv('superstore_sales.csv', parse_dates=['Order Date', 'Ship Date'])
raw_data.head()

Out[5]:

Row IDOrder IDOrder DateShip DateShip ModeCustomer IDCustomer NameSegmentCountryCityStatePostal CodeRegionProduct IDCategorySub-CategoryProduct NameSales
01CA-2017-1521562017-08-112017-11-11Second ClassCG-12520Claire GuteConsumerUnited StatesHendersonKentucky42420.0SouthFUR-BO-10001798FurnitureBookcasesBush Somerset Collection Bookcase261.9600
12CA-2017-1521562017-08-112017-11-11Second ClassCG-12520Claire GuteConsumerUnited StatesHendersonKentucky42420.0SouthFUR-CH-10000454FurnitureChairsHon Deluxe Fabric Upholstered Stacking Chairs,…731.9400
23CA-2017-1386882017-12-062017-06-16Second ClassDV-13045Darrin Van HuffCorporateUnited StatesLos AngelesCalifornia90036.0WestOFF-LA-10000240Office SuppliesLabelsSelf-Adhesive Address Labels for Typewriters b…14.6200
34US-2016-1089662016-11-102016-10-18Standard ClassSO-20335Sean O’DonnellConsumerUnited StatesFort LauderdaleFlorida33311.0SouthFUR-TA-10000577FurnitureTablesBretford CR4500 Series Slim Rectangular Table957.5775
45US-2016-1089662016-11-102016-10-18Standard ClassSO-20335Sean O’DonnellConsumerUnited StatesFort LauderdaleFlorida33311.0SouthOFF-ST-10000760Office SuppliesStorageEldon Fold ‘N Roll Cart System22.3680

Only a subset of the dataset will be used in this blog:

In [6]:

data = raw_data[['Order Date', 
                 'Ship Date', 
                 'Ship Mode', 
                 'Segment', 
                 'Country', 
                 'City', 
                 'State',
                 'Postal Code', 
                 'Region', 
                 'Category', 
                 'Sub-Category', 
                 'Sales']]
data.head()

Out[6]:

Order DateShip DateShip ModeSegmentCountryCityStatePostal CodeRegionCategorySub-CategorySales
02017-08-112017-11-11Second ClassConsumerUnited StatesHendersonKentucky42420.0SouthFurnitureBookcases261.9600
12017-08-112017-11-11Second ClassConsumerUnited StatesHendersonKentucky42420.0SouthFurnitureChairs731.9400
22017-12-062017-06-16Second ClassCorporateUnited StatesLos AngelesCalifornia90036.0WestOffice SuppliesLabels14.6200
32016-11-102016-10-18Standard ClassConsumerUnited StatesFort LauderdaleFlorida33311.0SouthFurnitureTables957.5775
42016-11-102016-10-18Standard ClassConsumerUnited StatesFort LauderdaleFlorida33311.0SouthOffice SuppliesStorage22.3680

In [7]:

data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 9800 entries, 0 to 9799
Data columns (total 12 columns):
 #   Column        Non-Null Count  Dtype         
---  ------        --------------  -----         
 0   Order Date    9800 non-null   datetime64[ns]
 1   Ship Date     9800 non-null   datetime64[ns]
 2   Ship Mode     9800 non-null   object        
 3   Segment       9800 non-null   object        
 4   Country       9800 non-null   object        
 5   City          9800 non-null   object        
 6   State         9800 non-null   object        
 7   Postal Code   9789 non-null   float64       
 8   Region        9800 non-null   object        
 9   Category      9800 non-null   object        
 10  Sub-Category  9800 non-null   object        
 11  Sales         9800 non-null   float64       
dtypes: datetime64[ns](2), float64(2), object(8)
memory usage: 918.9+ KB

The data ranges from 2015 to 2018. Data of the years 2016 and 2017 from the Order Date column will be used. Below two dataframes are the subsets for the years 2016 and 2017 respectively:

In [8]:

mask_2016 = (data['Order Date'] > '2016-01-01') & (data['Order Date'] <= '2016-12-30')
data_2016 = data.loc[mask_2016]

data_2016.head()

Out[8]:

Order DateShip DateShip ModeSegmentCountryCityStatePostal CodeRegionCategorySub-CategorySales
32016-11-102016-10-18Standard ClassConsumerUnited StatesFort LauderdaleFlorida33311.0SouthFurnitureTables957.5775
42016-11-102016-10-18Standard ClassConsumerUnited StatesFort LauderdaleFlorida33311.0SouthOffice SuppliesStorage22.3680
142016-11-222016-11-26Standard ClassHome OfficeUnited StatesFort WorthTexas76106.0CentralOffice SuppliesAppliances68.8100
152016-11-222016-11-26Standard ClassHome OfficeUnited StatesFort WorthTexas76106.0CentralOffice SuppliesBinders2.5440
242016-09-252016-09-30Standard ClassConsumerUnited StatesOremUtah84057.0WestFurnitureTables1044.6300

In [9]:

mask_2017 = (data['Order Date'] > '2017-01-01') & (data['Order Date'] <= '2017-12-30')
data_2017 = data.loc[mask_2017]

data_2017.head()

Out[9]:

Order DateShip DateShip ModeSegmentCountryCityStatePostal CodeRegionCategorySub-CategorySales
02017-08-112017-11-11Second ClassConsumerUnited StatesHendersonKentucky42420.0SouthFurnitureBookcases261.960
12017-08-112017-11-11Second ClassConsumerUnited StatesHendersonKentucky42420.0SouthFurnitureChairs731.940
22017-12-062017-06-16Second ClassCorporateUnited StatesLos AngelesCalifornia90036.0WestOffice SuppliesLabels14.620
132017-05-122017-10-12Standard ClassConsumerUnited StatesSeattleWashington98103.0WestOffice SuppliesBinders407.976
212017-09-122017-12-13Standard ClassCorporateUnited StatesFremontNebraska68025.0CentralOffice SuppliesArt19.460

Let’s pretend you work at a company and have to generate yearly reports from the sales dataset. The year 2017 will be addressed as current_year. The data for the previous year (2016) will be only used for comparisons.

The plan is to generate a report which includes the following metrics:

  1. Total sales + Comparison of Total sales with last year (text)
  2. Monthly sales + Percentage increase/decrease compared to the same month of last year (table)
  3. Segment (pie chart)
  4. Ship Mode (pie chart)
  5. Category (bar chart)

In [10]:

# giving names to the things that will be used to avoid hardcoding
current_year = 2017
current_year_data = data_2017
previous_year_data = data_2016

To begin with, let’s create our document. And while we are at it, let’s add a heading as well.

In [11]:

# creating the document
report_doc = Document()
# adding a heading of level=0
report_doc.add_heading(f'Yearly report {current_year}', level=0)

Out[11]:

<docx.text.paragraph.Paragraph at 0x7f244c4861c0>

In an ideal world, we would have all the functions ready, and then make the script to build up the MS Word document. But here I will make the functions as we proceed

In [12]:

# add a short intro paragraph
p_intro = report_doc.add_paragraph(f'This is the yearly report for the year {current_year}. We will analyse different metrics 
for sales, customer attributes, shipping modes and types of products.')

With that being done, let’s jump into the sales:

  1. We need the total sales for the current and previous years.
  2. We need the monthly sales for the current and previous years.
1. Total sale

Note: I’ll make use of functions as much as I can to keep things general and reproducible.

In [13]:

def get_yearly_sales(year_df: pd.DataFrame, col: str = 'Sales') -> float:
    """
    This function returns the sum of the sales rounded to 2 decimal places as a float.
    """
    return year_df[col].sum().round(2)

In [14]:

# use function to get total sales values
current_total_sales = get_yearly_sales(current_year_data)
previous_total_sales = get_yearly_sales(previous_year_data)
# calculate the percentage change in the total sales
percentage_change_total_sales = ((current_total_sales - previous_total_sales) / previous_total_sales * 100).round(1)

In [15]:

# let's spice it up a bit
if percentage_change_total_sales > 0:
    percentage_change_text = f"there has been an increase of {percentage_change_total_sales}% in total sales."
elif percentage_change_total_sales < 0:
    percentage_change_text = f"there has been an decrease of {abs(percentage_change_total_sales)}% in total sales."
else:
    percentage_change_text = f"there has been no change in the total sales."

Adding the total sales section (text) to the document:

In [16]:

# add a subheading
report_doc.add_heading('Sales:', level=1)

# add paragraph for the total sales
p_yearly_sales = report_doc.add_paragraph(f'The total sales for the year {current_year} is ')
p_yearly_sales.add_run(f"$ {current_total_sales}").bold = True
p_yearly_sales.add_run(f'. Compared to the year {current_year-1}, ')           
p_yearly_sales.add_run(percentage_change_text)  # this part of the text will depend on the conditions set above

Out[16]:

<docx.text.run.Run at 0x7f244c492df0>
2. Monthly sales:

Remember, for the monthly sales, we want it as a table. One way of making a table is using tuples as shown in the first script. Another way of doing it is by first creating a dataframe of what we want as a table. Then build a general function which converts any dataframe into docx tables. I’ll go with the latter.

2.1. Get monthly sales df:

In [17]:

def get_monthly_sales(year_df: pd.DataFrame, date_col: str = 'Order Date') -> pd.DataFrame:
    """
    This function returns a dataframe with the monthly sales rounded to 2 decimal places.
    """
    # group the date_col column by month and sum the values
    # filter out the 'Sales' column and round it up to 2 decimal places
    monthly_sales = year_df.groupby(pd.Grouper(key=date_col, freq='M')).sum()[['Sales']].round(2)
    
    # change the monthly dates into their corresponding month name 
    monthly_sales.index = monthly_sales.index.month_name()
    
    # reset index: this is important because ms word tables do not take df indices into account
    monthly_sales = monthly_sales.reset_index()
    
    # some aesthetics
    monthly_sales = monthly_sales.rename(columns={'Order Date': 'Month'})

    return monthly_sales

In [18]:

# use function to get the monthly sales
current_monthly_sales_df = get_monthly_sales(current_year_data)
previous_monthly_sales_df = get_monthly_sales(previous_year_data)
# add an extra column in 'current_monthly_sales_df' to show the percentage difference (rounded to 1dp) when compared to last year
current_monthly_sales_df['Percentage difference'] = ((current_monthly_sales_df['Sales'] - previous_monthly_sales_df['Sales']) / 
previous_monthly_sales_df['Sales'] * 100).round(1)
# add '%' sign after the percentages
# add '$' sign before the monthly sales values
current_monthly_sales_df['Percentage difference'] = current_monthly_sales_df['Percentage difference'].astype(str) + '%'
current_monthly_sales_df['Sales'] = '$' + current_monthly_sales_df['Sales'].astype(str)
2.2. Function to convert a df to a MS Word table:

In [19]:

def make_rows_bold(row):
    """
    This function sets all the cells in the 'row' to bold. 
    To be used to set the header row to bold.
    """
    for cell in row.cells:
        for paragraph in cell.paragraphs:
            for run in paragraph.runs:
                run.font.bold = True

In [20]:

def add_table_to_word_doc(input_df: pd.DataFrame, document):
    """
    This function converts the the input_df into a table, adds it to the input document, and then returns the document.
    """
    # this create a table of the size of the df
    table = document.add_table(input_df.shape[0]+1, input_df.shape[1])
    
    # convert the column names of the dataframe to column titles of the ms word table
    for col in range(input_df.shape[-1]):
        table.cell(0, col).text = input_df.columns[col]

    # add the rest of the dataframe to the table
    for row in range(input_df.shape[0]):
        for col in range(input_df.shape[-1]):
            table.cell(row+1, col).text = str(input_df.values[row, col])
    
    # style of the table ('Table Grid' is just the normal table)
    table.style = 'Table Grid'
    
    # set the title row to bold
    make_rows_bold(table.rows[0])

    return document

Now that we have got everything sorted, let’s add the monthly sales table to the document:

In [21]:

# add paragraph for monthly sales
p_monthly_sales = report_doc.add_paragraph(f'The table below shows the monthly sales for the year {current_year} and the percentage 
change compared to last year.')

# to add the table to the document, simply run the function
report_doc = add_table_to_word_doc(current_monthly_sales_df, report_doc)

# leave a space after table
p_break = report_doc.add_paragraph()
3. and 4. Now that sales is done, we need to add the pie charts for the Segment and Ship Mode. We need to make functions to aggregate them, and to make pie charts as well.

Function to get the aggregate:

In [22]:

def get_aggregate(input_df: pd.DataFrame, col_name: str):
    """
    This function groups the different values in the 'col_name' column and returns the counts for each of them.
    """
    return input_df[col_name].value_counts(dropna=False)

In [23]:

# getting the aggregates
current_year_segment_agg = get_aggregate(current_year_data, 'Segment')
current_year_ship_mode_agg = get_aggregate(current_year_data, 'Ship Mode')

Function to make and save the pie chart locally:

In [24]:

def make_pie_chart_and_save(input_series: pd.Series, picture_filename: str, legend_title: str, my_colours: list = None):
    # note: there are 3 ways to describe the colours
    # but I'll just use the defaults in this case
    # you can add a list of colours as to fourth parameter to customise it
#     my_colours = ['orange', '#6F95EB', (121/264, 216/264, 21/264)]

    # using the index as the labels 
    labels = input_series.index
    # making the pie chart
    pie = plt.pie(input_series.values, labels=labels, autopct='%1.1f%%', startangle=90, colors=my_colours)
    # customising the legend + moving it so that it does not block the pie chart
    plt.legend(pie[0], labels, title=legend_title + ':', bbox_to_anchor=(-0.3,1.0), loc='upper left', fontsize=10)
    # saving the pie chart locally
    plt.savefig(picture_filename + '.png')

In [25]:

# saving segment pie chart
make_pie_chart_and_save(current_year_segment_agg, 'segment_pie_chart', 'Segment')

# saving ship mode pie chart
make_pie_chart_and_save(current_year_ship_mode_agg, 'ship_mode_pie_chart', 'Ship Mode')

Now we can add the saved pie charts to the document as pictures. To set it to the same horizontal level, we first make a run and then wrap them into it.

In [26]:

# add paragraph for the pie charts
p_pie_charts = report_doc.add_paragraph(f'The pie charts below shows the proportions of the ')
p_pie_charts.add_run('Segments ').bold = True
p_pie_charts.add_run('and the ')
p_pie_charts.add_run('Shipping Modes ').bold = True
p_pie_charts.add_run(f'used for the year {current_year}:')
# add pie charts
pie_charts_run = report_doc.add_paragraph().add_run()
pie_charts_run.add_picture("segment_pie_chart.png", width=Inches(2.8))
pie_charts_run.add_picture("ship_mode_pie_chart.png", width=Inches(2.8))

Out[26]:

<docx.shape.InlineShape at 0x7f244c3b5d30>
5. Category/Types of products sold:

5.1. First we aggregate them using the previously built function.

In [27]:

# getting the aggregates
current_year_products_agg = get_aggregate(current_year_data, 'Category')

5.2. Then we make a function to make and save a bar chart.

In [28]:

def plot_bar_chart_and_save(input_series: pd.Series, picture_filename: str, my_colour: str = '#F8A331'):    
    # can be changed
#     my_colour = '#F8A331'
    
    ax = input_series.plot(rot=5, kind='bar', color=my_colour, title='Count of Category/Types of products sold')
    ax.bar_label(ax.containers[0])
    fig = ax.get_figure()
    fig.savefig(picture_filename + '.png')

In [29]:

# saving category bar chart
plot_bar_chart_and_save(current_year_products_agg, 'products_bar_chart')

Once we have everything ready, we can add the saved bar chart to the document as pictures.

In [30]:

# add a subtitle
report_doc.add_heading('Types of products:', level=1)
# add paragraph for the bar chart
p_products = report_doc.add_paragraph(f'The bar chart below shows the counts of the different types of products sold for 
the year {current_year}.')
# add bar charts
report_doc.add_picture('products_bar_chart.png', width=Inches(5.5))

Out[30]:

<docx.shape.InlineShape at 0x7f244c1cd670>

We are nearly done. Let’s add a brief conclusion to wrap this up.

In [31]:

if percentage_change_total_sales > 0:
    conclusion_text = f'an increase in total sales for the year {current_year}. Well done to the team and keep up the good work!'
else:
    conclusion_text = f'a loss for the year {current_year}. Please keep on pushing to meet the targets!'

p_conclusion = report_doc.add_paragraph('To conclude, it can be noted that the company made ')
p_conclusion.add_run(conclusion_text)

Out[31]:

<docx.text.run.Run at 0x7f244c1cd2b0>

Okay I think we are done now! Oh wait, we need to save it!

In [32]:

# save document locally
report_doc.save('yearly_report_2017.docx')

Full script below (as a general function):

In [33]:

def generate_yearly_report(current_year: int, current_year_data: pd.DataFrame, previous_year_data: pd.DataFrame):
   
    # creating the document
    report_doc = Document()
    # adding a heading of level=0
    report_doc.add_heading(f'Yearly report {current_year}', level=0)

    # add a short intro paragraph
    p_intro = report_doc.add_paragraph(f'This is the yearly report for the year {current_year}. We will analyse different 
    metrics for sales, customer attributes, shipping modes and types of products.')

    # use function to get total sales values
    current_total_sales = get_yearly_sales(current_year_data)
    previous_total_sales = get_yearly_sales(previous_year_data)
    # calculate the percentage change in the total sales
    percentage_change_total_sales = ((current_total_sales - previous_total_sales) / previous_total_sales * 100).round(1)

    # let's spice it up a bit
    if percentage_change_total_sales > 0:
        percentage_change_text = f"there has been an increase of {percentage_change_total_sales}% in total sales."
    elif percentage_change_total_sales < 0:
        percentage_change_text = f"there has been an decrease of {abs(percentage_change_total_sales)}% in total sales."
    else:
        percentage_change_text = f"there has been no change in the total sales."

    # add a subheading
    report_doc.add_heading('Sales:', level=1)

    # add paragraph for the total sales
    p_yearly_sales = report_doc.add_paragraph(f'The total sales for the year {current_year} is ')
    p_yearly_sales.add_run(f"$ {current_total_sales}").bold = True
    p_yearly_sales.add_run(f'. Compared to the year {current_year-1}, ')           
    p_yearly_sales.add_run(percentage_change_text)  # this part of the text will depend on the conditions set above

    # use function to get the monthly sales
    current_monthly_sales_df = get_monthly_sales(current_year_data)
    previous_monthly_sales_df = get_monthly_sales(previous_year_data)
    # add an extra column in 'current_monthly_sales_df' to show the percentage difference (rounded to 1dp) when compared to 
    last year current_monthly_sales_df['Percentage difference'] = ((current_monthly_sales_df['Sales'] - previous_monthly_sales_df['Sales']) / 
    previous_monthly_sales_df['Sales'] * 100).round(1)
    # add '%' sign after the percentages
    # add '$' sign before the monthly sales values
    current_monthly_sales_df['Percentage difference'] = current_monthly_sales_df['Percentage difference'].astype(str) + '%'
    current_monthly_sales_df['Sales'] = '$' + current_monthly_sales_df['Sales'].astype(str)

    # add paragraph for monthly sales
    p_monthly_sales = report_doc.add_paragraph(f'The table below shows the monthly sales for the year {current_year} and the 
    percentage change compared to last year.')

    # to add the table to the document, simply run the function
    report_doc = add_table_to_word_doc(current_monthly_sales_df, report_doc)

    # leave a space after table
    p_break = report_doc.add_paragraph()

    # getting the aggregates
    current_year_segment_agg = get_aggregate(current_year_data, 'Segment')
    current_year_ship_mode_agg = get_aggregate(current_year_data, 'Ship Mode')

    # saving segment pie chart
    make_pie_chart_and_save(current_year_segment_agg, 'segment_pie_chart', 'Segment')

    # saving ship mode pie chart
    make_pie_chart_and_save(current_year_ship_mode_agg, 'ship_mode_pie_chart', 'Ship Mode')

    # add paragraph for the pie charts
    p_pie_charts = report_doc.add_paragraph(f'The pie charts below shows the proportions of the ')
    p_pie_charts.add_run('Segments ').bold = True
    p_pie_charts.add_run('and the ')
    p_pie_charts.add_run('Shipping Modes ').bold = True
    p_pie_charts.add_run(f'used for the year {current_year}:')
    # add pie charts
    pie_charts_run = report_doc.add_paragraph().add_run()
    pie_charts_run.add_picture("segment_pie_chart.png", width=Inches(2.8))
    pie_charts_run.add_picture("ship_mode_pie_chart.png", width=Inches(2.8))

    # getting the aggregates
    current_year_products_agg = get_aggregate(current_year_data, 'Category')

    # saving category bar chart
    plot_bar_chart_and_save(current_year_products_agg, 'products_bar_chart')

    # add a subtitle
    report_doc.add_heading('Types of products:', level=1)
    # add paragraph for the bar chart
    p_products = report_doc.add_paragraph(f'The bar chart below shows the counts of the different types of products sold for 
    the year {current_year}.')
    # add bar charts
    report_doc.add_picture('products_bar_chart.png', width=Inches(5.5))

    if percentage_change_total_sales > 0:
        conclusion_text = f'an increase in total sales for the year {current_year}. Well done to the team and keep up the good work!'
    else:
        conclusion_text = f'a loss for the year {current_year}. Please keep on pushing to meet the targets!'

    p_conclusion = report_doc.add_paragraph('To conclude, it can be noted that the company made ')
    p_conclusion.add_run(conclusion_text)

    # save document locally
    report_doc.save('Yearly_Report_' + str(current_year) + '.docx')

In [34]:

generate_yearly_report(2017, data_2017, data_2016)

Conclusion:

Thank you all for spending your time reading my blog and trying it out. I hope you found it useful. Please note that the scenario that I used in the blog is quite a basic one. It can be made more complex, therefore providing more informed reports.

By Parwez Diloo
Published
Categorized as blog

Leave a comment

Your email address will not be published. Required fields are marked *