Code editing task: pandas_random_data
Topic: Math; change kind: adaptive
Libraries: pandas

Original code:
import pandas as pd
import random
import string


class GradeManipulator:

    def __init__(self):
        self.data = self._generate_random_data()

    def _generate_random_data(self):
        names = [''.join(random.choices(string.ascii_uppercase, k=5))
                 for _ in range(100)]
        ages = [random.randint(15, 25) for _ in range(100)]
        grades = random.choices(['A', 'B', 'C', 'D', 'F'], k=100)
        scores = [random.randint(0, 100) for _ in range(100)]

        return pd.DataFrame({
            'Name': names,
            'Age': ages,
            'Grade': grades,
            'Score': scores
        })

Instruction (descriptive):
Add two methods to the `GradeManipulator` class:
1. `average_score_by_grade(self)` - returns a DataFrame of the average "Score" column for each category of "Grade" (i.e., "A", "B", "C", "D", and "F"). Do not reset the index.
2. `top_scorers(self, n)` - returns a DataFrame of the n students with the highest "Score" values

Instruction (lazy):
Add two methods to the grade manipulator: `average_score_by_grade` and `top_scorers(n)`, 
which returns a data frame of the average score for each grade and a data frame of the top n students, respectively.

Edited code:
import pandas as pd
import random
import string


class GradeManipulator:

    def __init__(self):
        self.data = self._generate_random_data()

    def _generate_random_data(self):
        names = [''.join(random.choices(string.ascii_uppercase, k=5))
                 for _ in range(100)]
        ages = [random.randint(15, 25) for _ in range(100)]
        grades = random.choices(['A', 'B', 'C', 'D', 'F'], k=100)
        scores = [random.randint(0, 100) for _ in range(100)]

        return pd.DataFrame({
            'Name': names,
            'Age': ages,
            'Grade': grades,
            'Score': scores
        })

    def average_score_by_grade(self):
        return self.data.groupby('Grade')['Score'].mean()

    def top_scorers(self, n):
        return self.data.nlargest(n, 'Score')

Tests:
### START TESTS ###
if True: # pragma: no cover
    random.seed(42)
    dm = GradeManipulator()

    assert dm.data.shape == (100, 4), "Data shape is not as expected."

    top_3_scorers = dm.top_scorers(3)
    assert top_3_scorers.shape[0] == 3, "top_scorers does not return the correct number of top scorers."
    assert all(top_3_scorers.iloc[0]['Score'] >= score for score in top_3_scorers['Score']
               ), "top_scorers does not seem to order scores correctly."

    avg_scores = dm.average_score_by_grade()
    assert all(
        0 <= score <= 100 for score in avg_scores), "Average scores are out of range."

    expected_names = ['QAHFT', 'RXCKA', 'FNAFQ', 'OFPVA', 'USIEY', 'ICCWP', 'USNZJ', 'OVQWP', 'SBFHC', 'GCHQJ', 'JFGYQ', 'PESEJ', 'ZQORV', 'UFAIG', 'FYWIR', 'KXLGG', 'OGPXK', 'FZNCB', 'CQUKB', 'JZNZW', 'ASRNG', 'QCLLY', 'WGNEX', 'WHQPD', 'TOUNA', 'IAYWV', 'HBWYC', 'MBTTD', 'MOGWL', 'FOSFI', 'ZQLND', 'FIPFF', 'BQFXW', 'BGRFD', 'YOMUU', 'ECLLM', 'SRZCK', 'IWGEL', 'KHGYL', 'WOBZV', 'ZYWEM', 'FKBJZ', 'GULKY', 'ZOSEH', 'ZPOTB', 'PNWEY', 'CEPRG', 'DXGPQ', 'KPNYF',
                      'SGKRH', 'ITBLZ', 'ZBFGY', 'WWJEV', 'SPZRA', 'VHRYD', 'DCOHP', 'SFQGM', 'XVCLH', 'AUQGT', 'OLABW', 'XOVPD', 'DIXUW', 'XFGCU', 'WKQEY', 'WZVWA', 'TIYUW', 'VGUCW', 'WFVLH', 'UFAFI', 'WZHQK', 'ZNYCZ', 'EZGCL', 'SIPNK', 'OGSAY', 'NSTRJ', 'BRIIW', 'SHIKK', 'HDKYR', 'XQHOA', 'HLPRM', 'LFMXU', 'ECNQI', 'VTRFF', 'AGMWB', 'KQFSM', 'GRATU', 'CLEYN', 'BGWLU', 'RZPYX', 'PSNVO', 'XTMGG', 'QTNQH', 'CHHIO', 'DGSSB', 'KOKFK', 'XPSWT', 'JAJTW', 'YKTOP', 'FFLAI', 'RKEMD']
    assert list(dm.data['Name']) == expected_names, "Names don't match expected."

    expected_ages = [24, 23, 15, 21, 24, 24, 25, 15, 16, 25, 21, 17, 22, 17, 15, 19, 21, 20, 18, 22, 20, 20, 21, 19, 21, 19, 16, 22, 15, 23, 15, 20, 18, 25, 16, 25, 15, 15, 18, 18, 15, 24, 17, 18, 17, 22, 25, 16, 24, 18, 22, 19, 20,
                     17, 24, 24, 16, 17, 19, 16, 24, 15, 19, 24, 25, 21, 21, 18, 16, 24, 25, 18, 16, 19, 25, 24, 16, 24, 15, 20, 23, 21, 25, 20, 16, 23, 25, 20, 15, 21, 22, 16, 21, 20, 25, 22, 17, 21, 17, 23]
    assert list(dm.data['Age']) == expected_ages, "Ages don't match expected."

    expected_grades = ['F', 'B', 'F', 'C', 'C', 'C', 'D', 'B', 'F', 'F', 'A', 'F', 'B', 'C', 'D', 'B', 'A', 'F', 'A', 'B', 'D', 'B', 'F', 'D', 'B', 'A', 'F', 'A', 'D', 'C', 'D', 'D', 'D', 'C', 'D', 'A', 'B', 'D', 'B', 'C', 'C', 'C', 'C', 'D', 'B', 'D', 'B', 'B',
                       'A', 'A', 'A', 'C', 'D', 'A', 'B', 'C', 'D', 'F', 'C', 'B', 'A', 'A', 'B', 'A', 'A', 'C', 'B', 'F', 'C', 'D', 'A', 'F', 'C', 'F', 'C', 'C', 'C', 'A', 'A', 'F', 'C', 'F', 'C', 'A', 'D', 'A', 'A', 'C', 'B', 'F', 'A', 'D', 'D', 'D', 'B', 'C', 'C', 'C', 'F', 'F']
    assert list(dm.data['Grade']
                ) == expected_grades, "Grades don't match expected."

    expected_scores = [39, 72, 79, 7, 78, 94, 12, 97, 26, 80, 27, 33, 84, 10, 20, 30, 22, 70, 9, 20, 0, 52, 57, 88, 76, 60, 37, 4, 29, 36, 90, 36, 89, 58, 9, 87, 29, 33, 100, 80, 75, 84, 25, 54, 14, 69, 28, 82, 19, 34, 18, 9, 7, 21,
                       39, 76, 95, 72, 36, 56, 15, 59, 88, 38, 89, 51, 34, 64, 69, 63, 56, 10, 76, 5, 55, 94, 41, 77, 32, 3, 11, 29, 86, 73, 75, 2, 97, 86, 34, 73, 5, 97, 96, 22, 60, 66, 83, 56, 35, 23]
    assert list(dm.data['Score']
                ) == expected_scores, "Scores don't match expected."

    avg_scores = dm.average_score_by_grade()
    expected_avg_scores = [40.19047619047619, 55.27777777777778,
                           57.68, 51.78947368421053, 43.23529411764706]


    def round_to_2(x):
        return round(x, 2)


    assert list(
        map(round_to_2, avg_scores)) == list(map(round_to_2, expected_avg_scores)), "Average scores don't match expected."

    top_3_scorers = dm.top_scorers(3)
    expected_top_3_names = ['KHGYL', 'OVQWP', 'CLEYN']
    expected_top_3_scores = [100, 97, 97]
    assert list(
        top_3_scorers['Name']) == expected_top_3_names, "Top 3 names don't match expected."
    assert list(
        top_3_scorers['Score']) == expected_top_3_scores, "Top 3 scores don't match expected."


    # test empties
    top_0_scorers = dm.top_scorers(0)
    assert list(top_0_scorers['Name']) == [], "Top 0 names don't match expected."
    assert list(top_0_scorers['Score']) == [], "Top 0 scores don't match expected."
    avg_scores = dm.average_score_by_grade()