Help styling arrows in dataframe

Summary

Hi all, trying to change the colors of my arrows in the dataframe. Any help would be greatly appreciated.

Steps to reproduce

Code snippet:

def arrow_format(val):
    if val > 0:
        return f'↑ {val:.0f}%'
    elif val < 0:
        return f'↓ {abs(val):.0f}%'
    else:
        return f'{val:.0}%'


nice_table['from last month'] = nice_table['from last month'].apply(arrow_format)
nice_table['from last year'] = nice_table['from last year'].apply(arrow_format)
nice_table['from pre-pandemic (12/31/2019)'] = nice_table['from pre-pandemic (12/31/2019)'].apply(arrow_format)
st.dataframe(nice_table)

Actual behavior:

Would love the arrows to be red and green.
image

Hi @Daniel_Bishop :wave:

You can use a combination of applymap() and df.style.format() to add arrows and color cell values in one line.

  • df.style.format() formats the text displayed in value of cells.
  • .applymap() (elementwise): accepts a function that takes a single value and returns a string with the CSS attribute-value pair.

Note: I can only confirm this works for Streamlit versions 1.17.0 and the upcoming 1.18.0. Additionally, this solution colors the entire cell value, not just the arrows:

import numpy as np
import pandas as pd
import streamlit as st

def load_data():
    return pd.DataFrame(np.random.randint(-100, 100, size=(100, 2)), columns=list("ab"))

def _format_arrow(val):
    return f"{'↑' if val > 0 else '↓'} {abs(val):.0f}%" if val != 0 else f"{val:.0f}%"

def _color_arrow(val):
    return "color: green" if val > 0 else "color: red" if val < 0 else "color: black"

df = load_data()
st.dataframe(df)
styled_df = df.style.format(_format_arrow).applymap(_color_arrow, subset=["a", "b"])
st.dataframe(styled_df)

Hope this helps! :balloon:

3 Likes

Thanks for the quick help, snehankekre!

I am running into another issue. The styled columns are not able to be merged with another dataframe and keep the styling. Any potential solution?

Once you concat the dataframes, you need to re-apply the styling to columns that were previously styled. Extending my example from above:

import numpy as np
import pandas as pd

import streamlit as st


@st.cache_data
def load_data():
    # range of values from -100 to 100
    return pd.DataFrame(np.random.randint(-6, 6, size=(6, 2)), columns=list("ab"))


def _format_arrow(val):
    return f"{'↑' if val > 0 else '↓'} {abs(val):.0f}%" if val != 0 else f"{val:.0f}%"


def _color_arrow(val):
    return "color: green" if val > 0 else "color: red" if val < 0 else "color: black"

@st.cache_data
def second_df():
    df = pd.DataFrame(
        {
            "median_sale_price": 100000,
            "median_ppsf": 1000,
            "median_days_on_market": 30,
            "inventory": 100,
            "homes_above_list_price": 10,
            "homes_sold": 100,
        },
        index=[0],
    )
    df = df.T
    df[""] = df[0]
    df = df.drop(columns=[0])
    return df


col1, col2 = st.columns(2)

df = load_data()
col1.write("First dataframe")
col1.dataframe(df)
styled_df = df.style.format(_format_arrow).applymap(_color_arrow, subset=["a", "b"])
col2.write("Styled dataframe")
col2.dataframe(styled_df)

df_2 = second_df()
df_2 = df_2.reset_index()
col1.write("Second dataframe")
col1.dataframe(df_2)

# Concat the two dataframes df and df_2 into one dataframe
df_3 = pd.concat([df_2, df], axis=1)
col2.write("Concatenated dataframe")
col2.dataframe(df_3)

# Apply the styling to the concatenated dataframe
df_3 = df_3.style.format(_format_arrow, subset=["a", "b"]).applymap(
    _color_arrow, subset=["a", "b"]
)
st.write("Styled concatenated dataframe")
st.dataframe(df_3, use_container_width=True)

2 Likes

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.