TT Lab
Get started
Learn Learning paths Courses

Working With Customer Data

Two Different Averages: Rules for Missing and for Same

Continue in TT Lab

Goal

You build nulls.py, a tool that counts the notations of missing by kind, shows in numbers how the average changes when missing is filled with 0, separates exact duplicates, key duplicates, and things that are the same only to human eyes, and groups the same person by normalization, blocking, and judgment rules.

Why it matters

It is common for two averages to come out of looking at the same file. One side counted leaving out the rows with no value, and the other side counted filling them with 0. Neither raises an error, so nobody notices. That is why, when you state an average, you must state the denominator with it, and to do that you first have to count the cases per notation. 0 and missing are different facts. A customer with 0 won is "has not bought yet" and a customer with a blank cell is "we do not know". If the file says 0, you cannot tell from the file alone which of the two it is, so you count the cases separately, report them, and ask the business. Duplicates are not of one kind either. A line identical down to the character needs only one kept, but lines with the same identifier and different content cannot be fixed automatically, and the same thing with only the notation different has to be judged by rules. The most common accident here is merging just because the normalized values became the same. The grader does not trust your wording. It sets up a roster it made in a temporary directory, actually runs your tool, and checks the counts, the averages, and the grouped clusters against the values it counted itself. The names and amounts change on every run.

Steps

  1. Create and run /root/miss/gen_customers.py to produce /root/miss/raw/customers.csv. It contains missing notations and three kinds of duplicates.
  2. Build census <파일> <칼럼> (the placeholders are the file and the column) in /root/miss/nulls.py so that it counts the notations of missing by kind.
  3. Add stats <파일> <칼럼> so that it outputs side by side the average with missing filled with 0, the average with missing left out, and the average with 0s left out as well, and write the result in /root/miss/stats.json.
  4. Add aging <파일> so that it measures how a sentinel date wrecks the average processing time, and write the result in /root/miss/aging.json.
  5. Add dupes <파일> so that it counts exact duplicates and key duplicates separately.
  6. Add blocks <파일> so that after normalization it divides into blocks by the last four digits of the phone number, and outputs how far the candidate pairs to compare were reduced.
  7. Add match <파일> so that it groups the same person by judgment rules and even measures the pairs that blocking missed. Write the rules in /root/miss/rules.json.
  8. Process everything in one go to produce /root/miss/miss_report.json and /root/miss/miss_report.md.

Notes

Build the roster merged from three systems

Create and run /root/miss/gen_customers.py to produce /root/miss/raw/customers.csv. It contains five notations of missing, along with exact duplicates, key duplicates, and the same person written differently.

First build 36 people, and then put 12 of them in again with only the company name notation and the phone number format changed. Next add exact duplicates, copied straight from earlier lines, and lines that share only a record_id but are different people. Mix blanks, NULL, NA, hyphens, 0, and the sentinel date into the amount and the end date.

Count the notations of missing by kind

Build census <파일> <칼럼> (the placeholders are the file and the column) in /root/miss/nulls.py so that it outputs total, blank, tokens, zero, sentinel, and present. 0 is not counted as missing but counted separately.

Strip only the leading and trailing whitespace from the value, convert to uppercase, and compare with the list. The reason not to put 0 among the missing is the heart of this step — you cannot tell from the file alone whether it is a real 0 or a filled-in 0, so counting it and asking the business is the right thing.

The average filled with 0 and the average with missing left out

Add stats <파일> <칼럼> (the placeholders are the file and the column) so that it outputs the three averages side by side, and save the result for the amount column in /root/miss/stats.json.

The difference between the three averages is the denominator. If you fill missing with 0, the sum stays the same and only the denominator grows, so the average goes down. The average with the 0s left out as well has a smaller denominator, so the average goes up. All three numbers are right, and what is wrong is a report that does not say what was counted.

The thousands of years that 9999-12-31 made

Add aging <파일> (the placeholder is the file) so that it outputs together the average obtained by accepting the sentinel date as a date and the average with it left out, and save the result in /root/miss/aging.json.

9999-12-31 is not a date but a marker for 'not finished yet'. If you calculate with it as a date, the average becomes thousands of years. It looks silly, but if only a few are mixed in, the average quietly inflates and nobody notices. If the start date or the end date is missing, leave it out of the calculation and count it as unknown.

Separate exact duplicates from key duplicates

Add dupes <파일> (the placeholder is the file) so that it outputs rows, exact, key_dupes, and keys. exact is the excess beyond the first of identical lines that appear several times, and key_dupes is the number of keys for which record_id appears two or more times.

The reason to separate the two kinds is that their handling policies differ. A line identical down to the character needs only one kept, with nothing to judge, but for lines with the same identifier and different content, the machine does not know which side is right. Such keys must be left as a list and handed to the business.

Normalize and narrow the candidates by blocking

Add blocks <파일> (the placeholder is the file) so that it outputs rows, blocks, candidate_pairs, and pairs_without_blocking. The block key is the last four digits of the normalized phone number, and NONE if there are not four digits.

If you pair up and compare everything, the count grows in proportion to the square of the row count. If you first divide by a cheap key, the pairs to compare drop greatly. Normalization is for comparison and does not merge anything in this step — the merging is done by the rules of the next step.

Group the same person, and the pairs blocking missed

Add match <파일> (the placeholder is the file) so that it outputs rows, clusters, merged, groups, and recall_loss. Write the rules in /root/miss/rules.json as block_key, normalize, match, and not_match.

If you group rows whose email is empty as 'the emails are the same', dozens of different customers become one person — be sure to put 'non-empty' in the rule. recall_loss is the pairs found by exhaustive comparison minus the pairs found within blocks. It is the place to leave, as a number, the fact that blocking is not free.

Report missing values and duplicates on one sheet

Process everything in one go and write rows, missing, mean_naive, mean_known, exact, key_dupes, clusters, merged, and recall_loss in /root/miss/miss_report.json, and write /root/miss/miss_report.md in four sections: ## 없음은 몇 가지로 적혀 있었나 ## 0 으로 채우면 무엇이 달라지나 ## 무엇을 같은 것으로 봤나 ## 남은 위험 (the Korean headings mean "In how many ways was missing written", "What changes when you fill with 0", "What we treated as the same", and "The remaining risks").

missing is an object keyed by column name that holds the census result. Put in the two columns amount and closed_at. In the report, write the three averages together with their denominators, and write the number of pairs blocking missed under the remaining risks — nobody reports those pairs.