Statistical queries on a list of customers I have been given a list of simple Customer objects to work with. This would be an example of a customer list:

customers = [
    Customer(active=True, age=38, gender='F'),
    Customer(active=False, age=18, gender='M'),
    None,
    Customer(active=False, gender='F'),
    None,
    Customer(age=64),
    Customer(active=True, age=23),
    None,
    Customer(age=23, gender='M'),
    None,
]


The list contains None values for customers that have been deleted and active, gender and age are not required properties of Customer and so they can be un-set.

To find the number of active customers:

def number_of_active_customers(customers):
    # Assumption: A customer is only active if active=True is set
    return len([customer for customer in customers if customer is not None and customer.active is True])


To find the number of inactive customers:

def number_of_inactive_customers(customers):
    # Assumption: Deleted customers also count as inactive
    return len([customer for customer in customers if customer is None or customer.active in (False, None)])


To find out if the list of customers has any None values:

def list_has_none_values(customers):
    # any() won't work here since a list of None's always evaluates to False. We don't need to know the length
    # of the list so we can simply loop over each element and return True when we encounter the first None value
    for customer in customers:
        if customer is None:
            return True
    return False


To find out if the list has any customers with active=None:

def list_has_active_equals_none_customers(customers):
    # any() works here because we can stop processing at the first True value in the list
    return any([customer for customer in customers if customer is not None and customer.active is None])


To find the average age of male customers:

def average_age_of_male_customers(customers):
    try:
        # Round average to 1 digit
        return round(
            mean(
                (customer.age for customer in customers if customer is not None
                 and customer.gender == 'M' and isinstance(customer.age, int))
            ),
            1)
    except StatisticsError:
        # Return 0 when there are no male customers
        return 0


My code should be as pythonic and efficient as possible. I'm fairly sure I got it, I'm just second-guessing myself a little. This is not a school assignment and I am allowed to ask for help.
If you need to do more data analysis on this afterwards, then this might be a good time to learn about pandas which has data frames, allowing you to modify whole records at a time.

First, read in your list into a data frame:

import pandas as pd

df = pd.DataFrame([[customer.active, customer.age, customer.gender]
                   for customer in filter(None, customers)],
                  columns=["active", "age", "gender"])


Here the filter(None, it) filters out all None values (and all falsey values, but an object that exists is by default truthy, so unless you overwrote Customer.__eq__ or Customer.__bool__, it should be fine).

Now we have a data frame like this:

print(df)
#   active   age gender
# 0   True  38.0      F
# 1  False  18.0      M
# 2  False   NaN      F
# 3   None  64.0   None
# 4   True  23.0   None
# 5   None  23.0      M


To make handling of the left over None values easier, we replace them with numpy.nan:

import numpy as np

df = df.replace([None], np.nan)
print(df)
#   active   age gender
# 0   True  38.0      F
# 1  False   NaN      M
# 2  False   NaN      F
# 3    NaN  64.0    NaN
# 4   True  23.0    NaN
# 5    NaN  23.0      M


Now to get to your tasks:


Number of active customers:

active_users = df.active.sum()
# 2

Number of inactive customers:

deleted_users = sum(customer is None for customer in customers)
inactive_users = len(df) + deleted_users - active_users
# 8

Any customer deleted:

deleted_users &gt; 0
# True

Any customer with active=None:

df.active.isna().any()
# True

Average of male customers (NaNs are automatically ignored):

df[df.gender == "M"].age.mean()
# 20.5



If there are no people of that gender, it will return a numpy.nan instead of 0, though:

df[df.gender == "X"].age.mean()
# nan




From there on you can also do more fancy stuff:


Average age per gender:

df.groupby("gender").age.mean()
# gender
# F    38.0
# M    20.5
# Name: age, dtype: float64



Using this makes your code both more efficient to write (since most things you could want to do are already implemented) as well as faster to run (since the methods are usually implemented in C, instead of running in Python. So if you have lots of customers, this will certainly be better.