Skip to main content

Class 10 - Tests for tables & privacy analysis

Date: September 30, 2026

⚠️Homework
Homework 3 due October 4, 2026
🆕New Skills
First week for skills: 1, 2, 10, 11

Reference

For all work with tables, refer to the Tables page in the menu at the top of the page!

Skills: 0, 1, 3, 10​

Notes (review before class)​

Examples for Table-Producing Functions​

To test a function that produces a table (annotated Table), compare its result with the table you expect. For give_raises, from Class 9:

  • Simplify the input table: a small one, with just the columns the function uses, and enough variety in them:

    wages_test = table(["hourly-wage"], [[15], [20], [18], [18]])
  • Write computations in the expected table, rather than doing them by hand. The expected table goes right in the test, without a name (the input table can be made in the test, too):

    def test_give_raises():
    assert give_raises(wages_test) == table(
    ["hourly-wage"], [[15 * 1.1], [20], [18 * 1.1], [18 * 1.1]]
    )
  • Build the expected table from rows of an existing one: t.columns() is the list of t's column names, and table accepts dict rows, like those from row. Filtering employees to those working exactly 40 hours should give its first and fourth rows:

    emps_at_40 = table(employees.columns(), [employees.row(0), employees.row(3)])

lambda: Functions without Names​

Why not skip a helper like below_1k (Class 8)?

shuttle.filter(r["riders"] < 1000)
  • This is a NameError: r isn't defined. Arguments are evaluated before a function (or method) runs, so r["riders"] < 1000 is evaluated before filter starts. In below_1k, r is a parameter, so the body waits until r has a value.
  • lambda makes a temporary function, without a name: lambda r: means "a function that takes r", and the expression after the colon is what it returns:
shuttle.filter(lambda r: r["riders"] < 1000)
  • Same result as shuttle.filter(below_1k): filter calls whatever function it's given through its own parameter (say, keep(row)). Only below_1k gets an entry in the directory.
  • A lambda's body is one expression: no return, type annotations, or if. Use it for simple functions given to filter, transform_column, or add_column; write anything more as a named function, with the full design process. (Many, though not all, languages have lambda.)
  • Try: add 10 riders to each row of shuttle, with a lambda.

Charts​

Table methods also make charts (Tables lists them all). freq_bar_chart draws a bar for each different value in a column, as tall as the number of rows with that value:

employees.freq_bar_chart("hourly-wage")  # bars for 15, 18 (twice as tall), and 20

Announcement -- tests for your tests in autograder (10 mins)​

  • Start with HW3, our autograder runs against your tests, not just your code. Example of test grading

  • "Your tests against our correct implementation(s)" means we take your tests and make sure they pass on an implementation that works. For example, consider the nm_square function that is an example for Skill 1. If you wrote:

    def nm_square(n: float) -> float:
    """squares"""
    return n * n

    def test_nm_square():
    assert nm_square(1) == 1

    Then we might run your tests (just one!) against a correct implementation:

    def nm_square(n: float) -> float:
    """a correct square"""
    return n ** 2

    And in this case, all your tests pass, so you would get credit. If you had mis-interpreted the question as square root and had done:

    import math

    def nm_square(n: float) -> float:
    """squares"""
    return math.sqrt(n)

    def test_nm_square():
    assert nm_square(1) == 1
    assert nm_square(4) == 2

    Then you would not pass this, since when we combine our implementation with your tests, we get:

    def nm_square(n: float) -> float:
    """a correct square"""
    return n ** 2

    def test_nm_square():
    assert nm_square(1) == 1
    assert nm_square(4) == 2

    And the second test fails.

  • "Your tests against our incorrect implementation(s)" means we take your tests and run them on implementations that have bugs we know! We put them in! And the goal is that at least one of your tests fails. This means your tests are thorough enough to cover the buggy behavior in our implementation. If you had:

    def nm_square(n: float) -> float:
    """squares"""
    return n * n

    def test_nm_square():
    assert nm_square(1) == 1

    We might have an incorrect (i.e., buggy) implementation as:

    def nm_square(n: float) -> float:
    """an incorrect square -- just returns input"""
    return n

    But even though this definitely isn't square, when we combine your tests with our implementation:

    def nm_square(n: float) -> float:
    """an incorrect square -- just returns input"""
    return n

    def test_nm_square():
    assert nm_square(1) == 1

    All your tests (just one, here) pass! Which is not good. The reason they pass is that your tests were not good enough to distinguish between a function that just returned the input, doing nothing to it, and actually squaring. This test you have isn't wrong (so you shouldn't remove it!) -- it doesn't fail on the correct implementation -- but you should add more tests, to ensure you cover more behavior. Adding just one more test, to make your submission look like:

    def nm_square(n: float) -> float:
    """squares"""
    return n * n

    def test_nm_square():
    assert nm_square(1) == 1
    assert nm_square(2) == 4

    Is enough for you to catch our bug, since when we combine all your test with out buggy implementation, one of your tests now fails!

    def nm_square(n: float) -> float:
    """an incorrect square -- just returns input"""
    return n

    def test_nm_square():
    assert nm_square(1) == 1
    assert nm_square(2) == 4
    • As you see in the screenshot above, we may (and often do) have multiple incorrect implementations, that have different bugs. So if you catch some, but not all, think about what cases your tests aren't covering (for example, above I don't have any inputs that are negative. And I don't have zero!).

Recap (15 mins)​

A store takes 20% off every price under 100. Read through the program:

prices = table(
["price"],
[[50], [120], [80], [40], [50], [80], [80]],
)

def discount_price(price: float) -> float:
"""reduces price by 20%, if it is below 100"""
if price < 100:
return price * 0.8
else:
return price

def test_discount_price():
assert discount_price(50) == 50 * 0.8
assert discount_price(120) == 120

def apply_discounts(t: Table) -> Table:
"""transforms 'price' column by reducing 20%, if value is below 100"""
return t.transform_column("price", discount_price)

def test_apply_discounts():
test_table = table(["price"], [[50], [120], [80], [40]])
assert apply_discounts(test_table) == table(
["price"], [[50 * 0.8], [120], [80 * 0.8], [40 * 0.8]]
)

def discounted_rows(t: Table) -> Table:
"""the rows of t whose 'price' gets the discount"""
return t.filter(lambda r: r["price"] < 100)

def test_discounted_rows():
test_table = table(["price"], [[50], [120], [80]])
assert discounted_rows(test_table) == table(["price"], [[50], [80]])

apply_discounts(prices).freq_bar_chart("price")
  • Why is discount_price a named function, while discounted_rows uses a lambda?
  • In test_apply_discounts, why write 80 * 0.8 rather than 64.0? Which row of its test_table checks the else of discount_price?
  • How many bars does the chart have, and which value's bar is tallest?

The class exercise also includes a privacy analysis, using the method from the Class 6 Reading, which you've seen in labs but haven't yet done as an exercise in class.

Class Exercise (30 mins)​

  • Design a function that takes a table that has a "price" column and adds a new column "tax", which is the sales tax amount (look up the sales tax where you are; to calculate the sales tax rate, multiply that by the price. If you live somewhere with no sales tax, you can use 5%). You can assume there is not already a tax column.
  • Create a function to carry out the "obfuscation" exercise from last class, and write tests for it. Note, your test tables should only need an "item" column!
  • Consider our privacy analysis, which is based on answering five questions:
    QuestionAnswer
    What type of information is shared?
    Who is the subject of the information?
    Who is the sender of the information?
    Who are the potential recipients of the information?
    What principles govern the collection and transmission of this information?
  • Apply that analysis to the Shamazon resume tool that we covered in Class 6.

Wrap-up (5 mins)​

  • lambda allows us to more concisely use operations that expect functions as inputs, like transform_column, add_column, or filter, when the function is simple.
  • Table functions should follow the same design process, and include tests, just like other functions!