Es imposible estar siempre al 100%, incluso para la gente de élite. En todos los campeonatos, da igual el nivel, siempre hay gente que no está a su nivel, o personas que esperabas ver pero no están inscritas. Se lo dejan, o se toman una temporada más light, o lesiones... circunstancias individuales diferentes. Tanto es así que muchas veces a cierto nivel parece que solo con "sobrevivir" puedes mejorar posiciones: el embudo se va haciendo pequeño, solo hay que asegurarse de seguir. Pensando sobre eso me entró curiosidad por entender mejor cómo es este fenómeno:
  • ¿Es cierto que la gente va desapareciendo o es una percepción? 
  • ¿Cómo de grande es ese efecto?
  • ¿Ocurre en todos los estilos?
  • ¿Pasa más en algunas edades especialmente?
  • ¿Es igual en chicos que en chicas?
Seguramente todo esto ya está estudiado, o eso espero, pero como no lo he encontrado he intentado buscarme la vida. La solución que se me ha ocurrido es analizar los rankings nacionales. Por ejemplo: para un año de nacimiento concreto, en una prueba concreta, ¿cuántos de los nadadores que formaban parte del top100 al finalizar la temporada 2020-2021 siguen formando parte de ese ranking al finalizar la temporada 2023-2024? ¿Cuántos del top20 de la temporada 2020-2021 han desaparecido del ranking de este año?

Esas eran el tipo de pregunta que intentaba responder en un principio, pero como me suele pasar me he acabado liando. Haciendo esto he recogido muchísimos datos, y con ellos han surgido nuevas preguntas y métricas que he querido calcular. En la web de rankings de la RFEN se puede consultar rankings pero hacerlo atendiendo a muchos estilos y distancias, sexo y año de nacimiento supone mucho trabajo manual, y mucho tiempo, por no hablar de repetirlo si alguna vez quiero actualizar con temporadas nuevas u otros años de nacimiento. Además, después de recoger y tratar los datos hay que analizarlos, lo que supone aún más tiempo. Si no quieres seguir leyendo, al menos quédate con este mensaje: automatiza lo que vayas a hacer más de una vez, si puedes.

En este artículo voy a explicar cómo he accedido a la información de la base de datos de la RFEN, recogido los datos, los he convertido a un formato más manejable para mi y preparado para poder analizarlos. Después he creado una hoja de cálculo para que me extraiga toda la información que me interesa y por último un pequeño análisis de los datos. Este ha sido mi método. Existen muchos otros, pero este es el mío. Voy a compartir todo porque creo que podría resultar útil, pero si solo te interesa ver el resultado final, lo cual probablemente es lo más sabio, te recomiendo saltárte toda la parte técnica e ir directo al apartado de Datos y Análisis.

 

Preparación

Como ya he mencionado recoger los datos de forma manual a través de la web hubiera sido un disparo al pie. Con total seguridad me hubiera aburrido y quedado a medias. Necesitaba una forma automática, y para ello he creado un script que acceda a la API del Leverade (de donde la web de la RFEN recoge los tiempos) para pedir y guardar la información. Piensa que básicamente es como un robot que le diga a Leverade "dame el ranking de 100 espalda masculino de 2006 de la temporada 2020-2021. Vale, ahora igual pero de la 2021-2022...". Y así con todos. El robot entonces los guarda en un archivo, por ejemplo: 100_Espalda_2020-2021_male_2006.json.

Conseguir esto ha tenido algún truco. Por ejemplo Leverade no utiliza nombres intuitivos. La temporada 2023-2024 para Leverade es la 6653, mientras que la 2022-2023 es la 5828 por alguna razón. El orden de los estilos no es el correcto tampoco. Tampoco tiene mucha importancia. Una vez entendido como hay que pedirle las cosas a Leverade escribí este script que recoge los rankings de top100 de todas las pruebas en piscina de 50, para chicos y chicas nacidos entre 2006 y 2009 y para las temporadas 2020-2021 hasta la 2023-2024. Si quieres probarlo te advierto que puede llevar unos minutos de completar. En total 544 rankings diferentes. Más de 50,000 tiempos.

 
import requests
import json

# Define your variables

# Style IDs and their corresponding names:
style_mapping = {
    4: 'Espalda',
    5: 'Crol',
    6: 'Braza',
    7: 'Mariposa',
    8: 'Estilos'
}
STYLE_IDs = list(style_mapping.keys())

# Season IDs and their corresponding names:
season_mapping = {
    6653: '2023-2024',
    5828: '2022-2023',
    4919: '2021-2022',
    4130: '2020-2021'
}
SEASON_IDs = list(season_mapping.keys())

# Genders = 'male' or 'female'
GENDERS = ['female', 'male']

# Birthdates: Define multiple ranges
BIRTHDATE_RANGES = [
    ('2009-01-01', '2009-12-31'),
    ('2008-01-01', '2008-12-31'),
    ('2007-01-01', '2007-12-31'),
    ('2006-01-01', '2006-12-31')
]

# Goals = distancias
GOALS = {
    4: [50, 100, 200],
    5: [50, 100, 200, 400, 800, 1500],
    6: [50, 100, 200],
    7: [50, 100, 200],
    8: [200, 400]
}

# Loop over each combination of STYLE_ID, GOAL, SEASON_ID, GENDER, and BIRTHDATE_RANGE
for STYLE_ID in STYLE_IDs:
    for GOAL in GOALS[STYLE_ID]:
        for SEASON_ID in SEASON_IDs:
            for GENDER in GENDERS:
                for birthdate_from, birthdate_to in BIRTHDATE_RANGES:
                    # Extract the year from birthdate_from
                    birth_year = birthdate_from[:4]

                    # Construct the API URL with the variables
                    url = (f"https://api.leverade.com/managers/210453/ranking-licenses?goal={GOAL}&participants_amount=1"
                           f"&style_id={STYLE_ID}&season_id={SEASON_ID}&gender={GENDER}&office_id=1150"
                           f"&discipline_fields_pool_size=50&quantity=100&results_amount=best_result"
                           f"&profile_birthdate_from={birthdate_from}&profile_birthdate_to={birthdate_to}")

                    # Make the GET request to the API
                    response = requests.get(url)

                    # Check if the request was successful
                    if response.status_code == 200:
                        # Parse the response JSON
                        data = response.json()

                        # Define the output file name with the corresponding names
                        style_name = style_mapping[STYLE_ID]
                        season_name = season_mapping[SEASON_ID]
                        output_file = f'{GOAL}_{style_name}_{season_name}_{GENDER}_{birth_year}.json'

                        # Write the JSON data to a file
                        with open(output_file, 'w', encoding='utf-8') as file:
                            json.dump(data, file, ensure_ascii=False, indent=4)

                        print(f"Data has been written to {output_file}")
                    else:
                        print(f"Failed to retrieve data for STYLE_ID {STYLE_ID}, GOAL {GOAL}, SEASON_ID {SEASON_ID}, GENDER {GENDER}, "
                              f"BIRTHDATE {birthdate_from} to {birthdate_to}. HTTP Status code: {response.status_code}")
                        print(response.text)

 
Ahora tenía más de 500 archivos en formato JSON. Los JSON que proporciona Leverade están genial pero tienen mucha información que no me interesa. Mi meta en este punto era conseguir algo ordenado que pudiera exportar a Excel muy fácilmente y con la información mínima necesaria, y para eso prefiero el formato CSV. Así que siguiente objetivo: purgar los datos, quedarme solo con lo que necesito, y convertirlo a Valores Separados por Comas (CSV). Para hacer eso escribí otro script:

 
import os
import json
import csv

def json_to_csv(input_file):
    # Extract season from the file name
    base_name = os.path.basename(input_file)
    parts = base_name.replace('.json', '').split('_')
    season = parts[2]
    year = parts[4]

    # Load JSON data from file
    with open(input_file, 'r', encoding='utf-8') as file:
        data = json.load(file)

    # Define the output CSV file name
    output_file = input_file.replace('.json', '.csv')

    # Open the CSV file for writing
    with open(output_file, 'w', newline='', encoding='utf-8') as csvfile:
        # Define the fieldnames
        fieldnames = ['Ranking', 'Name', 'Goal', 'Style', 'Value', 'Season', 'Year']

        # Create a CSV DictWriter object
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

        # Write the header row
        writer.writeheader()

        # Write the data rows
        for index, entry in enumerate(data['data'], start=1):
            attributes = entry['attributes']
            row = {
                'Ranking': index,
                'Name': f"{attributes['profile_first_name']} {attributes['profile_last_name']}",
                'Goal': attributes['goal'],
                'Style': attributes['style_name'],
                'Value': attributes['value'] / 100.0,  # Convert value to float and divide by 100
                'Season': season,
                'Year': year
            }
            writer.writerow(row)

    print(f"Data has been written to {output_file}")

	# Remove the original JSON file
    os.remove(input_file)
    print(f"Original JSON file {input_file} has been removed.")


def process_all_jsons_in_folder():
    # Get the current directory where the script is located
    current_dir = os.path.dirname(__file__)

    # Iterate over all files in the current directory
    for filename in os.listdir(current_dir):
        if filename.endswith('.json'):
            input_file = os.path.join(current_dir, filename)
            json_to_csv(input_file)

# Usage
process_all_jsons_in_folder()
 

El resultado de ejecutar esto son los mismos 544 archivos pero ahora en CSV y con una información un poco más accesible. Para que se entienda mejor: la primera entrada del 50 espalda masculino de los nacidos en 2006 en la temporada 2021-2022 lucía así JSON:

 
{
    "data": [
        {
            "type": "ranking",
            "id": "14252436",
            "attributes": {
                "goal": 50,
                "style_id": "4",
                "style_name": "Espalda",
                "profile_id": "3088901",
                "profile_first_name": "TEO",
                "profile_last_name": "DEL RIEGO TORRES",
                "club_id": "4980271",
                "club_name": "C.N. TORRELAVEGA",
                "profile_birthdate": "2006-04-25",
                "value": 2720,
                "date": "2022-05-14 20:10:00",
                "location": "Gijón",
                "discipline_fields": {
                    "pool_size": "50",
                    "chronometer": "electronic"
                },
                "phaseresult_id": null,
                "phaseresult_resultable_type": null
            }
        },
 

Mientras que tras pasarlo a CSV y purgarlo ahora tenía esta forma:

 

Ranking,Name,Goal,Style,Value,Season,Year
1,TEO DEL RIEGO TORRES,50,Espalda,27.2,2021-2022,2006
 

Paso número 3 y último antes de pasar a Excel: no quería tener más de 500 archivos. En una primera iteración los junté por prueba, sexo y año de nacimiento, lo cual reducía considerablemente el número de archivos (x4 menos), pero trabajando sobre el Excel me di cuenta de que aún eran demasiados. Tenía que ir y venir muchas veces y manejar muchos documentos diferentes, así que decidí unirlos en un único archivo para cada sexo y dejar que Excel filtrara lo demás. ¿A que no adivinas cómo hice eso?

 
import os
import csv

def aggregate_csv_files(folder_path):
    # List to store all data
    aggregated_data_male = []
    aggregated_data_female = []

    # Iterate over all files in the specified folder
    for filename in os.listdir(folder_path):
        try:
            goal, style, season, gender, year = parse_filename(filename)
        except ValueError as e:
            print(f"Ignoring file {filename}: {e}")
            continue

        file_path = os.path.join(folder_path, filename)

        # Read the CSV file and append its data to aggregated_data
        with open(file_path, 'r', newline='', encoding='utf-8') as csvfile:
            reader = csv.DictReader(csvfile)
            if gender == 'male':
                for row in reader:
                    aggregated_data_male.append(row)
            elif gender == 'female':
                for row in reader:
                    aggregated_data_female.append(row)

        # After reading, optionally delete the CSV file
        os.remove(file_path)
        print(f"Processed and removed file: {file_path}")

    # Write aggregated data to separate CSV files for males and females
    write_aggregated_data(aggregated_data_male, "aggregated_data_male.csv", folder_path)
    write_aggregated_data(aggregated_data_female, "aggregated_data_female.csv", folder_path)

def write_aggregated_data(data_list, output_filename, folder_path):
    output_file_path = os.path.join(folder_path, output_filename)
    
    with open(output_file_path, 'w', newline='', encoding='utf-8') as csvfile:
        fieldnames = ['Ranking', 'Name', 'Goal', 'Style', 'Value', 'Season', 'Year']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        
        for row in data_list:
            writer.writerow(row)

    print(f"All aggregated data has been written to {output_filename}")

def parse_filename(filename):
    # Example filename format: 50_Braza_2020-2021_female_2009.csv
    parts = filename.replace('.json', '').replace('.csv', '').split('_')
    if len(parts) < 5:
        raise ValueError("Filename does not match expected format")

    goal = parts[0]
    style = parts[1]
    season = parts[2]
    gender = parts[3]
    year = parts[4]
    return goal, style, season, gender, year

# Example usage
folder_path = os.path.dirname(__file__)  # Assumes the script is in the folder with CSV files
aggregate_csv_files(folder_path)
 

Seguro que es más complicado de lo necesario, pero hace su trabajo. Por fin solo dos archivos con toda la información que necesito. Ahora, a pasarla a Excel y trabajarla. Más abajo puedes descargar el archivo si quieres echarle un vistazo. Aunque me hubiera gustado que respondiera a más temporadas automáticamente eso suponía complicar todo aún más. Aun así está el esqueleto básico, las formulas y el formato general para ampliarlo si llega el momento.

 

Datos y Análisis

  • En categoría masculina los nadadores listados, 2560 únicos, aparecen, de media, 2,6 veces en los top100 por temporada. Si al finalizar la temporada clasificas en 2 pruebas o menos, puede que estés por debajo de la media en cuanto a versatilidad (de los que aparecen al menos una vez). En categoría femenina hay 2401 nadadoras diferentes, mostrando una versatilidad muy parecida.
  • En categoría masculina (izquierda) el número de nadadores diferentes aumenta en cada generación posterior. Se podría interpretar que la gente es más especialista, pero ciñédome a la hipótesis del embudo y teniendo en cuenta la edad de los deportistas, creo que es justo lo contrario. No tendría sentido una mayor especialización en deportistas más jóvenes. Desaparece gente con la edad y eso favorece a que los que quedan suban puestos en pruebas que no han sido prioritarias para ellos. 
Nadadores únicos masculinos por generación
 

Es curioso como puede parecer que la categoría femenina (derecha) no parece seguir la misma tendencia, pero si miras temporada tras temporada para una sola generación la historia cambia. En especial si te fijas en las chicas más mayores: la generación del 2007 ha perdido 27 nadadoras desde la temporada 2020-2021, pero las del 2006 han perdido 96 (de 400), aproximadamente el 25% de sus integrantes. Solo en la temporada 2023-2024, 65.
 

Será interesante ver si la tendencia se mantiene para las chicas del 2007 durante la temporada 2024-2025 y posteriores, así como compararlo con los chicos, ya que su tasa de abandono a esa edad no parece tan grande.
 
  • Otra cosa que he calculado son los tiempos medios del top100, top50, top25 y top10 para cada generación y prueba. También se puede ver la tendencia de las marcas y comparar entre generaciones a mismas edades. Pongo un ejemplo: mariposa femenina. Parece que en el 200 aun que la gente super top sigue mejorando la mayoría tiende a estancarse alrededor de los 16, 17 años. 
 

Me gustaría mencionar el caso del crol también, ya que me ha llamado mucho la atención: no hay un solo ranking o prueba en que las chicas de 18 años hayan mejorado a las chicas de 17. Será interesante de comparar cuando haya más generaciones que hayan pasado por esas edades. Podría ser que las chicas del 2007 son más rápidas que las del 2006, como de hecho parece en muchos casos.
 


  • ¿Cuánta gente del top10 hace varias temporadas sigue entonces en el top10 hoy en día? Puedes encontrarlo en el Excel, pero te lo explico. Las siguientes tablas representan el % de deportistas que formaban parte del ranking en el topX en la temporada especificada que siguen formando parte de ese topX al finalizar la temporada 2023-2024 (los infantiles del 2009 aún no han acabado a fecha de escribir estas palabras).
    • Se puede interpretar que en la generación del 2006 (masculino), la mitad de los integrantes del top10 en 50 espalda en la temporada 2020-2021 ya no están en el top10 hoy en día.
    • Podría decirse que aproximadamente 7 de cada 10 integrantes del top10 en 200 espalda masculino en edad de formación pierden ese puesto en el transcurso de tres temporadas.
 


  • ¿Y cuántos deportistas prácticamente desaparecen? ¿Cuántos de los que estaban en el top25 ya no están ni siquiera en el top100?
Esta tabla representa al 100 braza femenino del 2009. Aproximadamente 1 de cada 4 de las chicas que formaron parte del top25 en la temporada 2020-2021 ya no está en el top100 de esta temporada, pero también se observa que las 10 integrantes del top10 de la temporada pasada siguen peleando esta temporada.

Estoy seguro de que se pueden extraer unas cuantas cosas más. Se me ocurre, por ejemplo, cuánto overlap hay entre los distintos estilos, es decir, si los mariposistas son más o menos los mismos que los crolistas, a diferencia de los bracistas, por decir algo. O manejando los datos de forma un poco diferente podría tener en cuenta los clubs, o las federaciones, y hacer esas comparativas, pero yo lo voy a dejar aquí de momento. Aquí abajo te dejo todos los archivos donde puedes ver los datos mejor y sacar tus propias conclusiones, o jugar con los archivos si quieres.

Por último, por contestar a las preguntas que me planteé al principio:

  • ¿Es cierto que la gente va desapareciendo o es una percepción? 
    • Parece ser cierto.
  • ¿Cómo de grande es ese efecto?
    • De moderado a grande en mi opinión.
  • ¿Ocurre en todos los estilos?
    • Sí. De hecho parece que la diferencia no es el estilo sino la distancia. A Mayor distancia, mayor efecto.
  • ¿Pasa más en algunas edades especialmente?
    • A mayor edad, mayor cantidad de gente que "desaparece".
  • ¿Es igual en chicos que en chicas?
    • Parece mayor en chicas.

Documentos

En esta carpeta de Drive puedes encontrar los tres scripts, dos archivos de Excel con los datos ya cargados para la categoría femenina y masculina, y los archivos CSV con todos los datos extraídos de Leverade.
 
  • Aqui tienes un pdf con los tiempos medios de las pruebas masculinas para las distintas edades y rankings.
  • Aquí tienes un pdf con los tiempos medios de las pruebas femeninas para las distintas edades y rankings.

Gracias por leer hasta aquí. Espero que te haya resultado interesante.

Si te ha gustado y te lo puedes permitir, ¡invítame a un café!