1

Preface

Though this problem references the pandas and numpy packages I believe a solution does not require working knowledge of either of these packages.

Setup

I wish to create a dictionary of lambda functions to pass to the formatter argument of the pandas function pandas.DataFrame.to_latex.

I would like the dictionary of lambda functions to format floats to a number of digits as specified by a list.

Example

What I would like to achieve may best be seen by example. Let's set up some floats we'd like to format:

import numpy as np
import pandas as pd

y = np.array([[0.12345, 0.12345, 0.12345]])
colnames = ['col1', 'col2', 'col3']
df = pd.DataFrame(y, columns=colnames)

#print(df)
#      col1     col2     col3
#0  0.12345  0.12345  0.12345

Excellent, now I'd like to format column col1 to show 1 digit after the decimal point. Similarly, I'd like to format col2 to show 2 digits after the decimal point and col3 to display 3 digits. Let's set up a list with this intention:

digits = [1, 2, 3]

From this list we shall create a dict of lambda functions to format the columns, and test the functions after creation.

fun = {}

for id, col in enumerate(['col1', 'col2', 'col3']):
    fun[col] = lambda x : '{{:.{}f}}'.format(digits[id]).format(x)
    print(fun[col](0.12345))
    # Prints 0.1, 0.12, 0.123 (looks to have worked!)

In the code above printing on creation of each entry appears I have achieved what I wished to. However, looking again I see I was mistaken

print(fun['col1'](0.12345)) # Prints 0.123
print(fun['col2'](0.12345)) # Prints 0.123
print(fun['col3'](0.12345)) # Prints 0.123

I understand that these functions all format the float the same as digits[id] = 3 after the loop.

I would like to alter how I create the lambda functions such that we instead observe:

print(fun['col1'](0.12345)) # Prints 0.1
print(fun['col2'](0.12345)) # Prints 0.12
print(fun['col3'](0.12345)) # Prints 0.123

Is it possible to do this? I imagine a solution may involve use of eval but I can't figure it out.

Obvious solution

Outside of the context of pd.DataFrame.to_latex, we could create a single lambda function which takes two arguments and formats floats as desired:

fun = lambda x, digit : '{{:.{}f}}'.format(digit).format(x)

print(fun(0.12345, digits[0])) # Prints 0.1
print(fun(0.12345, digits[1])) # Prints 0.12
print(fun(0.12345, digits[2])) # Prints 0.123

However, as far as I understand the formatter functions passed to pd.DataFrame.to_latex may only take a single argument and so such a solution would not be viable.

2
  • I guess that the title of your question is misleading. Probably you would like to know more about closures, so you will find several answers to your question for example here Commented Feb 6, 2019 at 11:00
  • @JoergVanAken You're right, I didn't know about closures and struggled to articulate because of this. Your link helped me understand and was very informative. Commented Feb 6, 2019 at 11:37

1 Answer 1

0

You need to bind the value of float resolution at the time of looping, otherwise the 3 lambda closures refers to the same id variable and all of them get the last value assigned to id.

A possible solution using functools.partial

def col_resolution(resolution, x):
    return '{{:.{}f}}'.format(resolution).format(x)

for id, col in enumerate(['col1', 'col2', 'col3']):
    fun[col] = partial(col_resolution, digits[id])

    # one line with lambda
    # fun[col] = partial(lambda res, x : '{{:.{}f}}'.format(res).format(x), digits[id])
Sign up to request clarification or add additional context in comments.

1 Comment

Excellent. This works as desired. I also found fun[col] = lambda x, digits=digits[id] : '{{:.{}f}}'.format(digits).format(x) binds digits[id] during the loop.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.