<jupyter_start><jupyter_text>Esta notebook esta destinada a analizar las respuestas de la api, entender los datos que vienen en los registros, filtrar los utiles y eliminar los que no necesitamos.Tuvimos un inconveniente a la hora de realizar esto que planteamos, y es que, al intentar cargar los resultados en memoria, desbordaba la RAM de la notebook.Por este motivo, fue necesario conectar el colab a una instancia especifica de Google, para esto, tuvimos que levantar una instancia en Google Cloud Platform, puntualmente una n1-highmem-4 ubicada en la zona de Google us-west1-b. Esta instancia tiene 4 cores, y 30gb de RAM. Recursos que fueron suficientes para poder trabajar con los resultados de la notebook de Querys.Con la desventaja que este .ipynb debe ser ejecutado con la cuenta donde está levantada la instancia en cuestión. Como no nos encontramos en la instancia tradicional que corre la notebook, es necesaio que nos autentiquemos de forma diferente para usar Google Cloud Storage. Asi que, nos autenticamos de la siguiente manera:<jupyter_code>!gcloud auth login<jupyter_output>Go to the following link in your browser:

    https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=32555940559.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fappengine.admin+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcompute+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Faccounts.reauth&state=aeyEUJpfJaPTnV3vi6QS1IR5YBTf61&prompt=consent&access_type=offline&code_challenge=mP6uSZe94akiVb-bu3AKbjM838PBVtXhMG6a8yFnkv0&code_challenge_method=S256

Enter verification code: 4/1AX4XfWhpPWfCmWVOXM1a76f5-NBBunhtoAdSoxV9MLOgaDmNaS1cCBBw1n8

You are now logged in as [<EMAIL>].
Your current project is [None].  You can change this setting by running:
  $ gcloud config set project PROJECT_ID<jupyter_text>Vamos a descargar el archivo de results del bucket, subido en la notebook anterior. De la misma forma que antes, declaramos dos variables, una con el nombre del proyecto y la otra con el nombre del bucket, y nos autenticamos.Y luego, copiamos del bucket al filesystem, el archivo results.pkl<jupyter_code>project_id = 'cryptic-opus-335323'
bucket_name = 'bdm-unlu'
!gsutil cp gs://{bucket_name}/querys/results.pkl results.pkl<jupyter_output>Copying gs://bdm-unlu/querys/results.pkl...
- [1 files][  1.4 GiB/  1.4 GiB]   38.2 MiB/s                                   
Operation completed over 1 objects/1.4 GiB.<jupyter_text>Como cada uno de los workers de la notebook anterior, hizo un append al archivo, no basta con hacer un unico load, para obtener los resultados, dado que cada worker hizo un dump, cada vez que guardó resultados de una petición.Por este motivo es que hacemos load del archivo, hasta que tire una exception, que será cuando el archivo ya no tenga mas nada para leer, lo que resulta en la exception "Ran out of input".<jupyter_code>import pickle
open_file_read = open("results.pkl", "rb")
raw_results = []
has_load = True
while has_load:    
  try:
    raw_results.extend(pickle.load(open_file_read))
  except Exception as e:
    print(e)
    has_load = False<jupyter_output>Ran out of input<jupyter_text>Corroboramos la cantidad de registros cargados en memoria<jupyter_code>len(raw_results)<jupyter_output><empty_output><jupyter_text>Sabemos que la respuesta de la API, tiene una clave llamada "attributes", donde se encuentran caracteristicas del inmueble.Y a su vez, cada atributo, tiene diferentes claves, ID, NAME, etc. que corresponden a ese atributo en particular.Esta función dado un unico registro y una clave, obtiene de esa respuesta, de todos los atributos, la clave indicada por parametro.<jupyter_code>def get_atributes(result, key):
  atributes_names = []
  for atribute in result["attributes"]:
    atributes_names.append(atribute[key])
  return atributes_names<jupyter_output><empty_output><jupyter_text>La funcion a continuación, dada una lista de resultados y una clave, obtiene la lista de atributos univoca, identificandolos por la clave dada.<jupyter_code>def get_unique_atribute_list(results, key = "name"):
  atribute_list = []
  for result in results:
    atributes = get_atributes(result, key)
    for atribute in atributes:
      if atribute not in atribute_list:
        atribute_list.append(atribute)
  return atribute_list<jupyter_output><empty_output><jupyter_text>Eso nos permite conocer, de los 308017 registros todos los posibles atributos que tienen los registros.<jupyter_code>get_unique_atribute_list(raw_results)<jupyter_output><empty_output><jupyter_text>Vemos que hay bastantes que son intuitivos, pero hay otros que ;laman la atencion. Como por ejemplo los de "Cantidad máxima de baños", "Cantidad máxima de dormitorios", ... Analizando encontramos que se trata de Emprendimientos, publicados en MercadoLibre, tales como [este](https://departamento.mercadolibre.com.ar/MLA-913664246-edificio-en-piedras-al-1300-san-telmo-_JM).Y encontramos que todos estos emprendimientos, tienen un atributo, cuyo ID es DEVELOPMENT_NAME.Para tratarlos, hicimos una funcion que determina si una respuesta determinada, contiene o no, en su lista de atributos, un valor especifico, en una clave determinada.<jupyter_code>def contains(result, key, value):
  for x in result:
    if x[key] == value:
      return True
  return False<jupyter_output><empty_output><jupyter_text>La utilizamos para ver, de todos los registros, cuales contenían en su lista de atributos, un atributo cuyo "id", sea DEVELOPMENT_NAME.Y guardamos en una lista llamada in_development, los inmuebles que eran emprendimientos, y los que no, en una lista llamada in_use.<jupyter_code>in_development = []
in_use = []

atribute_list = []
for raw_result in raw_results:
  if contains(raw_result["attributes"], "id", "DEVELOPMENT_NAME"):
    in_development.append(raw_result)
  else:
    in_use.append(raw_result)<jupyter_output><empty_output><jupyter_text>De esta manera, si vemos la longitud de la lista in_development, podremos ver, la cantidad de inmuebles que son de tipo emprendimiento, publicados en MercadoLibre.<jupyter_code>len(in_development)<jupyter_output><empty_output><jupyter_text>Si ahora vemos la lista univoca de atributos, de los inmuebles que quedaron en la lista in_use, vemos que ya no aparecen los atributos que nos llamaban la atención, tales como "Cantidad mínima de metros cubiertos", "Cantidad mínima de ambientes", etc.<jupyter_code>get_unique_atribute_list(in_use)<jupyter_output><empty_output><jupyter_text>Un valor que llama medianamente la atención es 'Tour virtual', por lo que verificamos si se trata de un valor atipico, o si la mayoría de los registros lo tienen.<jupyter_code>records_with_virtual_tour = 0
for value in in_use:
  if contains(value["attributes"], "id", "WITH_VIRTUAL_TOUR"):
    records_with_virtual_tour += 1
records_with_virtual_tour<jupyter_output><empty_output><jupyter_text>Vemos que dicha cantidad tienen este atributo, y analizando, vimos que se trata de un video, mostrando el interior del departamento y la entrada del edificio. Por ultimo, el atributo de "Número de oficinas", llama la atención, por lo que también hicimos un analisis, para ver de que se trata.<jupyter_code>records_with_office_number = []
for value in in_use:
  if contains(value["attributes"], "name", "Número de oficinas"):
    records_with_office_number.append(value)
len(records_with_office_number)<jupyter_output><empty_output><jupyter_text>Vemos que solo esa cantidad de registros, tienen este atributo, por lo que es un atributo bastante propenso a ser borrado.Obtenemos el permalink para poder ver en la web de que inmueble se trata<jupyter_code>records_with_office_number[0]["permalink"]<jupyter_output><empty_output><jupyter_text>Y vemos que son propiedades utilizadas como oficina, por lo que los removemos de la lista de registros.<jupyter_code>for record_with_office_number in records_with_office_number:
  in_use.remove(record_with_office_number)<jupyter_output><empty_output><jupyter_text>Y si ahora corroboramos la lista de atributos, vemos que esta limpia, con todos atributos que nos parecen válidos para describir un inmueble, de la forma que queremos, para el analisis que estamos buscando realizar.<jupyter_code>get_unique_atribute_list(in_use)<jupyter_output><empty_output><jupyter_text>Hasta aca un analisis de los diferentes atributos que contenian los registros, y una primera aproximación al filtrado.---Ahora vamos a seleccionar los atributos mas importantes y armar un primer DataFrame. Obtenemos la lista de atributos univocos, pero ahora utilizando el id como identificador, y no el name. Y la guardamos en una variable llamada property_attributes.<jupyter_code>property_attributes = get_unique_atribute_list(in_use, "id")<jupyter_output><empty_output><jupyter_text>Esta funcion, dado un registro y una lista de claves, encuentra en la respuesta de la API el valor buscado, o devuelve None.<jupyter_code>def fetch_nested_value(record, keys):  
  try:
    for key in keys: 
      record = record.get(key) 
    return record
  except: 
    return None<jupyter_output><empty_output><jupyter_text>Este bloque de codigo, dado un registro y un valor de ID determinado,, encuentra el atributo cuyo id es igual al pasado por parametro.<jupyter_code>def fetch_attribute_by_id(record, attr_id): 
  try:
    for attr in record["attributes"]: 
      if (attr["id"] == attr_id):
        return attr["value_name"]
  except:
    return None<jupyter_output><empty_output><jupyter_text>Esta es la funcion que mapea de un registro de la API, con campos que nos interesan y campos que no, a un diccionario de python, unicamente con campos que son utiles.Siempre contiene los atributos presentes en property_attributes, en caso de no estar en la respuesta de la API, los asigna con valor None.<jupyter_code>def process_record(record, expected_attributes): 
  result = {}    
  result["title"] = record["title"]
  result["permalink"] = record["permalink"]

  result["city"] = fetch_nested_value(record, ["location","city","name"])
  result["state"] = fetch_nested_value(record, ["location","state","name"])
  result["latitude"] = fetch_nested_value(record, ["location","latitude"])
  result["longitude"] = fetch_nested_value(record, ["location","longitude"])
  result["neighborhood"] = fetch_nested_value(record, ["location","neighborhood","name"])
  
  result["seller_id"] = fetch_nested_value(record, ["seller","id"])
  result["seller_city"] = fetch_nested_value(record, ["seller_address","city","name"])
  result["seller_state"] = fetch_nested_value(record, ["seller_address","state","name"])
  result["real_estate_agency"] = fetch_nested_value(record, ["seller","real_estate_agency"])
  result["seller_cancelations"] = fetch_nested_value(record, ["seller","seller_reputation","metrics","cancellations","value"])
  result["seller_claims"] = fetch_nested_value(record, ["seller","seller_reputation","metrics","claims","value"])
  result["seller_handling_time"] = fetch_nested_value(record, ["seller","seller_reputation","metrics","delayed_handling_time","value"])
  result["seller_sales"] = fetch_nested_value(record, ["seller","seller_reputation","metrics","sales","completed"])
  
  result["currency_id"] = record["currency_id"]
  result["price"] = record["price"]

  for attr in expected_attributes:
    result[attr.lower()] = fetch_attribute_by_id(record, attr)
  
  return result<jupyter_output><empty_output><jupyter_text>Procesa todos los registros, y los guarda en una lista (records)<jupyter_code>records = []
for record in in_use:
  try:
    records.append(process_record(record, property_attributes))
  except Exception as e:    
    print(e)<jupyter_output><empty_output><jupyter_text>Importamos la libreria de pandas, y creamos un dataframe a partir de la lista de diccionarios.<jupyter_code>import pandas as pd
pd.set_option('display.max_colwidth', 150)
data = pd.DataFrame.from_dict(records)<jupyter_output><empty_output><jupyter_text>Imprimimos el resultado de hacerle un head al dataframe mencionado.<jupyter_code>data.head()<jupyter_output><empty_output><jupyter_text>Guardamos el dataset en un archivo llamado dataset.csv, y lo guardamos en el bucket, en el path bdm-unlu/attributes/dataset.csv<jupyter_code>data.to_csv("dataset.csv")

!gsutil cp dataset.csv gs://{bucket_name}/attributes/dataset.csv<jupyter_output>Copying file://dataset.csv [Content-Type=text/csv]...
- [1 files][ 97.5 MiB/ 97.5 MiB]                                                
Operation completed over 1 objects/97.5 MiB.
{'fraction_non_alphanumeric': 0.08643144683874023, 'fraction_numerical': 0.01049806614571, 'mean_word_length': 6.121978639685216, 'pattern_counts': {'":': 0, '<': 94, '<?xml version=': 0, '>': 94, 'https://': 2, 'lorem ipsum': 0, 'www.': 5, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '24', 'dir': 'jupyter-structured-clean-dedup', 'id': '592196', 'license': 'MIT', 'n_tokens_mistral': 4329, 'n_tokens_neox': 4217, 'n_words': 1227, 'path': 'Atributes.ipynb', 'repo_name': 'AgustinNormand/BDM_TPI', 'size': '12683'}