def clean_data(df):
    """
    Description: This function cleans the merged pandas DataFrame containing disaster response messages

    Arguments:
        df:  pandas DataFrame containing disaster response messages

    Returns:
        cleaned pandas DataFrame
    
    Cleaning steps:
        - NaN values:
            - Nan values are only present in 'original' column
            - if original message wasn't in english -> no imputation necessary, rows will not be dropped
            
        - Column 'child_alone':
            - Values are zero in all rows
            - Shouldn't cause an error, therefore column will not be dropped
            
        - Column 'related':
            - Column is a bit messed up
            - Figure eight source: https://www.figure-eight.com/dataset/combined-disaster-response-data/
            - According to column description, the column can contain two values (yes/no)
            - In provided dataset the column contains three values (0, 1, 2)
            - Running df.groupby(by='related').sum() shows that for related=2, all category columns are 0
            - Also related label is partly set incorrectly (e.g. 0 although some category columns are 1)
            - related label will be replaced by a self generated label using the rule: 0 if all category columns are zero, else 1
            
        - Dropping duplicates:
            - Full duplicates and duplicates of ID's will be dropped in two steps
    """
    
    # Fix column 'related'
    category_columns = df.drop(columns=['id', 'related', 'message', 'original', 'genre']).columns.tolist()
    df['related_new'] = df[category_columns].sum(axis=1).clip(upper=1)
    
    N_false_labels = len(df[df['related']!=df['related_new']])
    
    if N_false_labels > 0:
        print('In {:d} rows, the "related" label is set incorrectly.\nValues will be corrected'.format(N_false_labels))
        df.drop(columns='related', inplace=True)
        df.rename(columns={'related_new': 'related'}, inplace=True)
    else:
        df.drop(columns='related_new', inplace=True)
    
    
    # Drop duplicates (full rows)
    N_full_duplicates = len(df) - len(df.drop_duplicates())
    if N_full_duplicates > 0:
        print('Dropping {:d} rows with full duplicates'.format(N_full_duplicates))
        df.drop_duplicates(inplace=True)
    
    # Drop duplicates (ID's)
    N_id_duplicates = len(df) - len(df.drop_duplicates(subset='id'))
    if N_id_duplicates > 0:
        print('Dropping {:d} rows with duplicate ID but divergent labels'.format(N_id_duplicates))
        df.drop_duplicates(subset='id', inplace=True)
    
    return df