没有和有样式的电子邮件数据框

Email dataframes without and with Styling

我正在 运行 进行一些测试,以使用 2 个数据帧从我的 python 脚本发送电子邮件:一个是原始的(即没有样式),另一个是带有样式的。对于样式,我遵循 pandas-style documentation

中的示例(输出 9)

我copy/pasted样式函数的定义如下:

def highlight_max(s):
    '''
    highlight the maximum in a Series yellow.
    '''
    is_max = s == s.max()
    return ['background-color: yellow' if v else '' for v in is_max]

def color_negative_red(val):
    """
    Takes a scalar and returns a string with
    the css property `'color: red'` for negative
    strings, black otherwise.
    """
    color = 'red' if val < 0 else 'black'
    return 'color: %s' % color

我为 运行 我的测试创建了一个虚拟数据框

np.random.seed(24)
df = pd.DataFrame({'A': np.linspace(1, 10, 10)})
df = pd.concat([df, pd.DataFrame(np.random.randn(10, 4), columns=list('BCDE'))],
           axis=1)
df.iloc[3, 3] = np.nan
df.iloc[0, 2] = np.nan

这里是标准的 send_email 函数:

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import smtplib

def send_email():
    df1 = df.copy()
    styler = df1.style.applymap(color_negative_red).apply(highlight_max).hide_index()

    msg = MIMEMultipart()
    msg['Subject'] = "This is a test"
    msg['From'] = 'abc@dfe.com'
    msg['To'] = 'abc@dfe.com'

    html = """
    <html>
        <head></head>
        <body>
            <p>Hello!<br>
                <br>
                Raw:<br>
                {0}
                <br>
                Formated<br>
               {1}
            </p>
        </body>
    </html>
    """.format(df.to_html(), styler.render())

    part1 = MIMEText(html, 'html')
    msg.attach(part1)

    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.ehlo()
    server.starttls()
    server.login(the_user, the_password)
    server.send_message(msg)
    server.quit()

我希望格式化的数据框显示时没有索引,最大行值以黄色突出显示,负数以红色突出显示。

这是我 sent/received:

发送的电子邮件的屏幕截图

所以格式化的数据框确实没有索引,但是黄色的最大高亮和红色的负数格式丢失了。我在 styler 变量中遗漏了什么吗?例如,我需要使用 .set_table_styles() 吗?如果可以,怎么做?

编辑:这里是 styler.render() 的输出,至少是它的顶部

<style  type="text/css" >
#T_181a6_row0_col0,#T_181a6_row0_col1,(...){
            color:  black;
       }

#T_181a6_row0_col3,#T_181a6_row0_col4,(...){
            color:  red;
       }
#T_181a6_row2_col4,#T_181a6_row6_col2,(...){
            color:  black;
            background-color:  yellow;
       }
</style><table id="T_181a6_" ><thead>    
<tr>
<th class="col_heading level0 col0" >A</th>        
<th class="col_heading level0 col1" >B</th>        
<th class="col_heading level0 col2" >C</th>        
<th class="col_heading level0 col3" >D</th>        
<th class="col_heading level0 col4" >E</th>    
</tr></thead><tbody>
<tr>
    <td id="T_181a6_row0_col0" class="data row0 col0" >1.000000</td>
    <td id="T_181a6_row0_col1" class="data row0 col1" >1.329212</td>
    <td id="T_181a6_row0_col2" class="data row0 col2" >nan</td>
    <td id="T_181a6_row0_col3" class="data row0 col3" >-0.316280</td>
    <td id="T_181a6_row0_col4" class="data row0 col4" >-0.990810</td>
</tr>
(...)
</tbody></table>

Edit2:对 html 代码的格式感到抱歉,我不确定我该怎么做...

Edit3:当我将 styler.render() 的输出保存为 html 并在 google 中打开该文件时,例如数据帧格式正确。所以现在我想知道问题是否在于我如何将 styler.render() 包含到 html body (?) 我注意到我有 < body >.. .< / body > 包含在另一个 < body >...< / body > 中是正确的吗?

编辑 4:html 变量的输出保存为 html 并在 chrome 中打开:

所以问题是 webmail 正在剥离样式标题,它应该在 html 的每个 td 内。有没有办法在 render() 函数中做到这一点?

好的,感谢@Nathan 和@tripleee 指出核心问题。我已经安装了库 premailer,它允许转换 html 文档并将样式从 部分移动到 html 代码的相关部分。我现在可以在我的电子邮件中收到格式正确的 datfaram。

我提出的解决方案如下所示:

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import smtplib
from premailer import transform

def send_email():
    styler = (df_details.style.apply(highlight_same_week, axis=None).hide_index()).render()

    msg = MIMEMultipart()
    msg['Subject'] = "This is a test"
    msg['From'] = 'abc@dfe.com'
    msg['To'] = 'abc@dfe.com'

    html = """
    <html>
        <head></head>
        <body>
            <p>Hello!<br>
                <br>
                Raw:<br>
                {0}
                <br>
                Formated<br>
               {1}
            </p>
        </body>
    </html>
    """.format(df.to_html(), styler)

    html = transform(html)
    part1 = MIMEText(html, 'html')
    msg.attach(part1)

    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.ehlo()
    server.starttls()
    server.login(the_user, the_password)
    server.send_message(msg)
    server.quit()

和 highlight_same_week 看起来像这样:

    def highlight_same_week(s):
        return pd.DataFrame(df_template.values, columns=s.columns)

所以 df_details 是具有我感兴趣的样式值的数据框,而 df_template 是与 df_details 具有相同尺寸的数据框,但它的值是样式元素,例如:

df_template.iloc[x, y] = 'background-color: #ffff00'

所以 fix/correct 的要点是:

  • 使用正确的函数创建样式器变量(send_email 函数的第一行)
  • 使用具有所需样式的数据框模板(这里我只用黄色高亮显示两列值在同一周内的行,基于 datetime.isocalendar())
  • 使用预邮程序库中的转换函数转换我的原始 HTML 变量