From bce550b3bded02cd4aaee44f04f8f09568720f4e Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Wed, 12 Oct 2022 21:56:50 -0300 Subject: [PATCH 1/3] OT302-15 - Optimize MapReduce --- dev_rf/big_data/logs/logs.log | 5 - .../{ => with_chunks}/big_data_logging.py | 0 dev_rf/big_data/with_chunks/chunks.py | 50 ++++++++ dev_rf/big_data/{ => with_chunks}/logger.cfg | 0 dev_rf/big_data/with_chunks/logs/logs.log | 33 ++++++ .../with_chunks/logs/logs.log.2022-10-12_19 | 15 +++ dev_rf/big_data/with_chunks/main.py | 64 +++++++++++ dev_rf/big_data/with_chunks/mapper.py | 107 ++++++++++++++++++ dev_rf/big_data/{ => with_chunks}/reducer.py | 0 .../with_chunks/results/AverageAnswerTime.csv | 2 + .../results/MostUsedTags.csv | 0 .../results/Top10MostViewedPosts.csv | 0 dev_rf/big_data/with_chunks/save_into_file.py | 25 ++++ dev_rf/big_data/{ => with_chunks}/shuffler.py | 0 .../without_chunks/big_data_logging.py | 29 +++++ dev_rf/big_data/without_chunks/logger.cfg | 35 ++++++ dev_rf/big_data/without_chunks/logs/logs.log | 5 + dev_rf/big_data/{ => without_chunks}/main.py | 0 .../big_data/{ => without_chunks}/mapper.py | 5 +- dev_rf/big_data/without_chunks/reducer.py | 60 ++++++++++ .../results/AverageAnswerTime.csv | 0 .../without_chunks/results/MostUsedTags.csv | 11 ++ .../results/Top10MostViewedPosts.csv | 11 ++ dev_rf/big_data/without_chunks/shuffler.py | 31 +++++ 24 files changed, 480 insertions(+), 8 deletions(-) delete mode 100644 dev_rf/big_data/logs/logs.log rename dev_rf/big_data/{ => with_chunks}/big_data_logging.py (100%) create mode 100644 dev_rf/big_data/with_chunks/chunks.py rename dev_rf/big_data/{ => with_chunks}/logger.cfg (100%) create mode 100644 dev_rf/big_data/with_chunks/logs/logs.log create mode 100644 dev_rf/big_data/with_chunks/logs/logs.log.2022-10-12_19 create mode 100644 dev_rf/big_data/with_chunks/main.py create mode 100644 dev_rf/big_data/with_chunks/mapper.py rename dev_rf/big_data/{ => with_chunks}/reducer.py (100%) create mode 100644 dev_rf/big_data/with_chunks/results/AverageAnswerTime.csv rename dev_rf/big_data/{ => with_chunks}/results/MostUsedTags.csv (100%) rename dev_rf/big_data/{ => with_chunks}/results/Top10MostViewedPosts.csv (100%) create mode 100644 dev_rf/big_data/with_chunks/save_into_file.py rename dev_rf/big_data/{ => with_chunks}/shuffler.py (100%) create mode 100644 dev_rf/big_data/without_chunks/big_data_logging.py create mode 100644 dev_rf/big_data/without_chunks/logger.cfg create mode 100644 dev_rf/big_data/without_chunks/logs/logs.log rename dev_rf/big_data/{ => without_chunks}/main.py (100%) rename dev_rf/big_data/{ => without_chunks}/mapper.py (98%) create mode 100644 dev_rf/big_data/without_chunks/reducer.py rename dev_rf/big_data/{ => without_chunks}/results/AverageAnswerTime.csv (100%) create mode 100644 dev_rf/big_data/without_chunks/results/MostUsedTags.csv create mode 100644 dev_rf/big_data/without_chunks/results/Top10MostViewedPosts.csv create mode 100644 dev_rf/big_data/without_chunks/shuffler.py diff --git a/dev_rf/big_data/logs/logs.log b/dev_rf/big_data/logs/logs.log deleted file mode 100644 index de170e5..0000000 --- a/dev_rf/big_data/logs/logs.log +++ /dev/null @@ -1,5 +0,0 @@ -"08/10/2022" - INFO - root - Starting the main module... -"08/10/2022" - INFO - root - Starting the mapper module... -"08/10/2022" - INFO - root - Starting the shuffler module... -"08/10/2022" - INFO - root - Starting the reducer module... -"08/10/2022" - INFO - root - Took 1.3 seconds to finish the tasks diff --git a/dev_rf/big_data/big_data_logging.py b/dev_rf/big_data/with_chunks/big_data_logging.py similarity index 100% rename from dev_rf/big_data/big_data_logging.py rename to dev_rf/big_data/with_chunks/big_data_logging.py diff --git a/dev_rf/big_data/with_chunks/chunks.py b/dev_rf/big_data/with_chunks/chunks.py new file mode 100644 index 0000000..59c516b --- /dev/null +++ b/dev_rf/big_data/with_chunks/chunks.py @@ -0,0 +1,50 @@ +import xml.etree.ElementTree as ET +from big_data_logging import configured_logger + +logger = configured_logger() + + +def chunks(list, n=10): + """Yield n number of striped chunks from list. + Parameters + ---------- + list : list + List that must be chunked + n : int + Number of chunks + + Returns + ------- + yield : generator + Generator with the different chunks + + """ + + for i in range(0, n): + yield list[i::n] + + +def generate_chunks(): + """Function that creates a generator with the different chunks + + Returns + ------- + generator : generator + returns a generator with the different chunks of the xml file + """ + + logger.info("Starting the chunks module...") + + # Load and parse the posts.xml file + tree = ET.parse("./112010 Meta Stack Overflow/posts.xml") + # Get the root of the xml + root = tree.getroot() + + list_to_chunk = [] + + # Loop into each row element + for child in root: + # Get the attributes of each row element + list_to_chunk.append(child.attrib) + + return chunks(list_to_chunk) diff --git a/dev_rf/big_data/logger.cfg b/dev_rf/big_data/with_chunks/logger.cfg similarity index 100% rename from dev_rf/big_data/logger.cfg rename to dev_rf/big_data/with_chunks/logger.cfg diff --git a/dev_rf/big_data/with_chunks/logs/logs.log b/dev_rf/big_data/with_chunks/logs/logs.log new file mode 100644 index 0000000..b3eb164 --- /dev/null +++ b/dev_rf/big_data/with_chunks/logs/logs.log @@ -0,0 +1,33 @@ +"12/10/2022" - INFO - root - Starting the main module... +"12/10/2022" - INFO - root - Starting the chunks module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Took 11.4 seconds to finish the tasks +"12/10/2022" - INFO - root - Starting the shuffler module... +"12/10/2022" - INFO - root - Starting the reducer module... +"12/10/2022" - INFO - root - Took 11.5 seconds to finish the tasks +"12/10/2022" - INFO - root - Starting the main module... +"12/10/2022" - INFO - root - Starting the chunks module... +"12/10/2022" - INFO - root - Took 0.9 seconds to finish the tasks +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Took 10.9 seconds to finish the tasks +"12/10/2022" - INFO - root - Starting the shuffler module... +"12/10/2022" - INFO - root - Starting the reducer module... +"12/10/2022" - INFO - root - Took 11.0 seconds to finish the tasks diff --git a/dev_rf/big_data/with_chunks/logs/logs.log.2022-10-12_19 b/dev_rf/big_data/with_chunks/logs/logs.log.2022-10-12_19 new file mode 100644 index 0000000..9752eb3 --- /dev/null +++ b/dev_rf/big_data/with_chunks/logs/logs.log.2022-10-12_19 @@ -0,0 +1,15 @@ +"12/10/2022" - INFO - root - Starting the main module... +"12/10/2022" - INFO - root - Starting the chunks module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the shuffler module... +"12/10/2022" - INFO - root - Starting the reducer module... +"12/10/2022" - INFO - root - Took 10.8 seconds to finish the tasks diff --git a/dev_rf/big_data/with_chunks/main.py b/dev_rf/big_data/with_chunks/main.py new file mode 100644 index 0000000..aab70a4 --- /dev/null +++ b/dev_rf/big_data/with_chunks/main.py @@ -0,0 +1,64 @@ +import time + +from chunks import generate_chunks +from mapper import mapper +from shuffler import shuffler +from reducer import reducer +from big_data_logging import configured_logger +from save_into_file import save_into_file + +logger = configured_logger() + + +def main(): + """Main function that calls the mapper, shuffler and reducer functions and then save to csv the results""" + logger.info("Starting the main module...") + + start_time = time.time() + + # Create chunks + chunks = generate_chunks() + + duration = time.time() - start_time + logger.info(f"Took {round(duration,1)} seconds to finish the tasks") + + # Initialize lists for the results + post_views_list = [] + mapped_tags_list = [] + score_answertime_list = [] + + for chunk in chunks: + # Use the mapper function to map the different tasks + post_views, mapped_tags, score_answertime = mapper(chunk) + + # Append result to list of results + post_views_list.append(post_views) + mapped_tags_list.append(mapped_tags) + score_answertime_list.append(score_answertime) + + duration = time.time() - start_time + logger.info(f"Took {round(duration,1)} seconds to finish the tasks") + + # Make the lists of lists only one list + post_views_list = sum(post_views_list, []) + mapped_tags_list = sum(mapped_tags_list, []) + score_answertime_list = sum(score_answertime_list, []) + + # Use the shuffler function to shuffle the mapped tasks + post_views_list, mapped_tags_list, score_answertime_list = shuffler( + post_views_list, mapped_tags_list, score_answertime_list + ) + + # Use the reducer function to reduce the shuffled tasksS + top10_post_views, tags_reduced, average_answer_time = reducer( + post_views_list, mapped_tags_list, score_answertime_list + ) + + save_into_file(top10_post_views, tags_reduced, average_answer_time) + + duration = time.time() - start_time + logger.info(f"Took {round(duration,1)} seconds to finish the tasks") + + +if __name__ == "__main__": + main() diff --git a/dev_rf/big_data/with_chunks/mapper.py b/dev_rf/big_data/with_chunks/mapper.py new file mode 100644 index 0000000..b48ea80 --- /dev/null +++ b/dev_rf/big_data/with_chunks/mapper.py @@ -0,0 +1,107 @@ +import xml.etree.ElementTree as ET +import datetime +from big_data_logging import configured_logger + +logger = configured_logger() + + +def get_answer_dict(): + """Function to get the answers ids with the creation dates + + Parameters + ---------- + root : object + Element Tree root object + + Returns + ------- + answer_dict : dict + Dictionary that has as key the answer_id and as value the creation_date + + """ + # Load and parse the posts.xml file + tree = ET.parse("./112010 Meta Stack Overflow/posts.xml") + # Get the root of the xml + root = tree.getroot() + # Initialize variable + answer_dict = {} + + # Loop into each row to get the answer_id and creation_date + for child in root: + dict = child.attrib + + # PostTypeId == 2 means that it is an answer. PostTypeId == 1 means is a question. + if dict["PostTypeId"] == "2": + answer_dict[dict["Id"]] = dict["CreationDate"] + + return answer_dict + + +def mapper(chunk): + """Function used to map the 3 required tasks: + - 1 - Top 10 posts views + - 2 - Top 10 words in tags + - 3 - Score and answer time + + Returns + ------- + post_views : list + List of dicts. Each dict has key 'Id' and 'ViewCount'. + mapped_tags : list + List of dicts. Each dict has as key and tag and a value 1. + score_answertime : list + List of dicts. Each dict has key 'Score' and 'ResponseTime' in hours. + + """ + logger.info("Starting the mapper module...") + + # Initialize variables + post_views = [] + mapped_tags = [] + score_answertime = [] + + # Get the answer dict. key=answer_id, value=CreationDate + answer_dict = get_answer_dict() + + # Loop into each row element + for dict in chunk: + + # 1 - Top 10 posts views + # Append to the list the post_id and the view_count of each post + post_views.append({"Id": dict["Id"], "ViewCount": int(dict["ViewCount"])}) + + # 2 - Top 10 words in tags + # If the post has a tag replace the <> and split to get the different words. + try: + tags = dict["Tags"].replace("<", " ").replace(">", " ").strip().split() + # Map each individual tag + for tag in tags: + mapped_tags.append({tag: 1}) + except: + # If the post hasn't a tag then continue + continue + + # 3 - Score and answer time + # If the post is a question + if dict["PostTypeId"] == "1": + # Get question score and creation_time + post_score = int(dict["Score"]) + post_creation_time = datetime.datetime.fromisoformat(dict["CreationDate"]) + try: + # Some posts haven't an accepted answer, so they will be skipped as they do not have an AcceptedAnswerId + + # Get the accepted_answer_id + accepted_answer_id = dict["AcceptedAnswerId"] + + # With the accepted_answer_id go to the answer_dict and take the creation_date value and transform it to datetime + accepted_answer_time = datetime.datetime.fromisoformat(answer_dict[accepted_answer_id]) + + # Calculate response time from question creation to accepted answer creation (in hours) + response_time = round((accepted_answer_time - post_creation_time).seconds / 3600, 2) + + # Append the score and response time to a list of dicts + score_answertime.append({"Score": post_score, "ResponseTime": response_time}) + except: + continue + + return post_views, mapped_tags, score_answertime diff --git a/dev_rf/big_data/reducer.py b/dev_rf/big_data/with_chunks/reducer.py similarity index 100% rename from dev_rf/big_data/reducer.py rename to dev_rf/big_data/with_chunks/reducer.py diff --git a/dev_rf/big_data/with_chunks/results/AverageAnswerTime.csv b/dev_rf/big_data/with_chunks/results/AverageAnswerTime.csv new file mode 100644 index 0000000..ea1b963 --- /dev/null +++ b/dev_rf/big_data/with_chunks/results/AverageAnswerTime.csv @@ -0,0 +1,2 @@ +Average time to get an accepted answer (hours) +5.9158 diff --git a/dev_rf/big_data/results/MostUsedTags.csv b/dev_rf/big_data/with_chunks/results/MostUsedTags.csv similarity index 100% rename from dev_rf/big_data/results/MostUsedTags.csv rename to dev_rf/big_data/with_chunks/results/MostUsedTags.csv diff --git a/dev_rf/big_data/results/Top10MostViewedPosts.csv b/dev_rf/big_data/with_chunks/results/Top10MostViewedPosts.csv similarity index 100% rename from dev_rf/big_data/results/Top10MostViewedPosts.csv rename to dev_rf/big_data/with_chunks/results/Top10MostViewedPosts.csv diff --git a/dev_rf/big_data/with_chunks/save_into_file.py b/dev_rf/big_data/with_chunks/save_into_file.py new file mode 100644 index 0000000..1e3e163 --- /dev/null +++ b/dev_rf/big_data/with_chunks/save_into_file.py @@ -0,0 +1,25 @@ +import os +import pandas as pd + + +def save_into_file(top10_post_views, tags_reduced, average_answer_time): + # Create resultss folder if it doesnt exist + cwd = os.getcwd() + + try: + if not os.path.exists(cwd + "/results"): + os.makedirs(cwd + "/results") + except: + print("Folder cannot be created") + + # Save to a csv the Top 10 most viewed posts + top10_post_views_df = pd.DataFrame(top10_post_views) + top10_post_views_df.to_csv("./results/Top10MostViewedPosts.csv", index=False) + + # Save to a csv the Top 10 tags + tags_reduced_df = pd.DataFrame(tags_reduced, columns=["Tag", "Count"]) + tags_reduced_df.to_csv("./results/MostUsedTags.csv", index=False) + + # Save to a csv the Average time to get an accepted answer + average_answer_time_serie = pd.Series(average_answer_time, name="Average time to get an accepted answer (hours)") + average_answer_time_serie.to_csv("./results/AverageAnswerTime.csv", index=False) diff --git a/dev_rf/big_data/shuffler.py b/dev_rf/big_data/with_chunks/shuffler.py similarity index 100% rename from dev_rf/big_data/shuffler.py rename to dev_rf/big_data/with_chunks/shuffler.py diff --git a/dev_rf/big_data/without_chunks/big_data_logging.py b/dev_rf/big_data/without_chunks/big_data_logging.py new file mode 100644 index 0000000..730ed31 --- /dev/null +++ b/dev_rf/big_data/without_chunks/big_data_logging.py @@ -0,0 +1,29 @@ +import logging +from logging import config +import os + + +def configured_logger(): + """This function is going to setup the ogger using the logger.cfg file. + The logger has 2 functions: + 1 - Display the logging messages in the console + 2 - Save the files to a log file every week (Every Sunday)""" + + # Get current working directory + cwd = os.getcwd() + + # Create logs folder if it does not exist + try: + if not os.path.exists(cwd + "/logs"): + os.makedirs(cwd + "/logs") + except: + print("Folder cannot be created") + + # Load the logger.cfg file + # cwd + "big_data/logger.cfg" + config.fileConfig("logger.cfg") + + # Create logger with the configuration + logger = logging.getLogger("root") + + return logger diff --git a/dev_rf/big_data/without_chunks/logger.cfg b/dev_rf/big_data/without_chunks/logger.cfg new file mode 100644 index 0000000..42da32e --- /dev/null +++ b/dev_rf/big_data/without_chunks/logger.cfg @@ -0,0 +1,35 @@ +[loggers] +keys=root + +[handlers] +keys=consoleHandler, fileHandler + +[formatters] +keys=myFormatter + +[logger_root] +level=INFO +handlers=consoleHandler, fileHandler + +# Handler to display in console +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=myFormatter +args=(sys.stdout,) + +#Handler to save the log in files +[handler_fileHandler] +class=handlers.TimedRotatingFileHandler +level=INFO +formatter=myFormatter +# Create a new log every Sunday and have 1 log file as backup +when='W6' +interval=1 +backupCount=1 +args=("logs/logs.log",) + +# Formatter +[formatter_myFormatter] +format=%(asctime)s - %(levelname)s - %(name)s - %(message)s +datefmt="%d/%m/%Y" \ No newline at end of file diff --git a/dev_rf/big_data/without_chunks/logs/logs.log b/dev_rf/big_data/without_chunks/logs/logs.log new file mode 100644 index 0000000..6b66577 --- /dev/null +++ b/dev_rf/big_data/without_chunks/logs/logs.log @@ -0,0 +1,5 @@ +"12/10/2022" - INFO - root - Starting the main module... +"12/10/2022" - INFO - root - Starting the mapper module... +"12/10/2022" - INFO - root - Starting the shuffler module... +"12/10/2022" - INFO - root - Starting the reducer module... +"12/10/2022" - INFO - root - Took 1.5 seconds to finish the tasks diff --git a/dev_rf/big_data/main.py b/dev_rf/big_data/without_chunks/main.py similarity index 100% rename from dev_rf/big_data/main.py rename to dev_rf/big_data/without_chunks/main.py diff --git a/dev_rf/big_data/mapper.py b/dev_rf/big_data/without_chunks/mapper.py similarity index 98% rename from dev_rf/big_data/mapper.py rename to dev_rf/big_data/without_chunks/mapper.py index 1012cd8..f49d9e1 100644 --- a/dev_rf/big_data/mapper.py +++ b/dev_rf/big_data/without_chunks/mapper.py @@ -20,8 +20,6 @@ def get_answer_dict(root): """ - logger.info("Starting the mapper module...") - # Initialize variable answer_dict = {} @@ -52,7 +50,8 @@ def mapper(): List of dicts. Each dict has key 'Score' and 'ResponseTime' in hours. """ - # ./112010 Meta Stack Overflow/posts.xml + logger.info("Starting the mapper module...") + # Load and parse the posts.xml file tree = ET.parse("./112010 Meta Stack Overflow/posts.xml") # Get the root of the xml diff --git a/dev_rf/big_data/without_chunks/reducer.py b/dev_rf/big_data/without_chunks/reducer.py new file mode 100644 index 0000000..0117093 --- /dev/null +++ b/dev_rf/big_data/without_chunks/reducer.py @@ -0,0 +1,60 @@ +import collections +from statistics import mean +from big_data_logging import configured_logger + +logger = configured_logger() + + +def reducer(post_views, mapped_tags, score_answertime): + """Function to reduce the results that come from shuffler + + Parameters + ------- + post_views : list + List of dicts. Each dict has key 'Id' and 'ViewCount'. It is sorted + mapped_tags : list + List of dicts. Each dict has as key and tag and a value 1. It is sorted + score_answertime : list + List of dicts. Each dict has key 'Score' and 'ResponseTime' in hours. It is sorted + + Returns + ------- + top10_post_views : list + List of dicts. Only the Top 10 + mapped_tags : list + List of dicts. Only the Top 10 + score_answertime : float + Result of the average time to get an accepted answer of the top 200 questions by highest score (hour) + + """ + + logger.info("Starting the reducer module...") + + # Get the 10 most viewed posts + top10_post_views = post_views[0:10] + + # Use a Counter to reduce the tags + counter = collections.Counter() + for d in mapped_tags: + counter.update(d) + + # Transform the counter object to a dictionary + tags_reduced = {} + for key, value in counter.items(): + tags_reduced[key] = value + + # Sort the reduced tags and get the top 10 + tags_reduced = sorted(tags_reduced.items(), key=lambda x: x[1], reverse=True)[0:10] + + # Get the top 200 answers by score + score_answertime = score_answertime[0:200] + + # Get the top 200 answers response_time + answer_times = [] + for dict in score_answertime: + answer_times.append(dict["ResponseTime"]) + + # Calculate the average response time + average_answer_time = mean(answer_times) + + return top10_post_views, tags_reduced, average_answer_time diff --git a/dev_rf/big_data/results/AverageAnswerTime.csv b/dev_rf/big_data/without_chunks/results/AverageAnswerTime.csv similarity index 100% rename from dev_rf/big_data/results/AverageAnswerTime.csv rename to dev_rf/big_data/without_chunks/results/AverageAnswerTime.csv diff --git a/dev_rf/big_data/without_chunks/results/MostUsedTags.csv b/dev_rf/big_data/without_chunks/results/MostUsedTags.csv new file mode 100644 index 0000000..206fc7d --- /dev/null +++ b/dev_rf/big_data/without_chunks/results/MostUsedTags.csv @@ -0,0 +1,11 @@ +Tag,Count +discussion,5791 +feature-request,4590 +support,2984 +bug,2648 +status-completed,1836 +stackoverflow,1527 +tags,973 +reputation,945 +questions,762 +area51,692 diff --git a/dev_rf/big_data/without_chunks/results/Top10MostViewedPosts.csv b/dev_rf/big_data/without_chunks/results/Top10MostViewedPosts.csv new file mode 100644 index 0000000..26e7b4f --- /dev/null +++ b/dev_rf/big_data/without_chunks/results/Top10MostViewedPosts.csv @@ -0,0 +1,11 @@ +Id,ViewCount +28625,33344 +37328,28372 +31913,26601 +9134,20536 +1777,19695 +2267,15180 +7931,12584 +61142,9918 +20420,8903 +53346,8619 diff --git a/dev_rf/big_data/without_chunks/shuffler.py b/dev_rf/big_data/without_chunks/shuffler.py new file mode 100644 index 0000000..62d46a4 --- /dev/null +++ b/dev_rf/big_data/without_chunks/shuffler.py @@ -0,0 +1,31 @@ +from big_data_logging import configured_logger + +logger = configured_logger() + + +def shuffler(post_views, mapped_tags, score_answertime): + """Function to shuffle the results that come from mapper + + Parameters and Returns (sorted) + ------- + post_views : list + List of dicts. Each dict has key 'Id' and 'ViewCount'. + mapped_tags : list + List of dicts. Each dict has as key and tag and a value 1. + score_answertime : list + List of dicts. Each dict has key 'Score' and 'ResponseTime' in hours. + + """ + + logger.info("Starting the shuffler module...") + + # Sort post_views from higher views to lower views + post_views = sorted(post_views, key=lambda x: x["ViewCount"], reverse=True) + + # Sort word_tags by alphabetical order + mapped_tags = sorted(mapped_tags, key=lambda d: list(d.keys())) + + # Sort score_answertime from higher score to lower score + score_answertime = sorted(score_answertime, key=lambda x: x["Score"], reverse=True) + + return post_views, mapped_tags, score_answertime From 87999a9a19ede005803071f3da89054fc7d79ab1 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Mon, 17 Oct 2022 19:17:30 +0200 Subject: [PATCH 2/3] OT302-127 - Big data testing --- dev_rf/testing/logger/logger_config.cfg | 40 +++++ dev_rf/testing/logger_cfg.py | 22 +++ dev_rf/testing/mapreduce_big_data.py | 188 ++++++++++++++++++++++ dev_rf/testing/resultados_map_reduce.jpeg | Bin 0 -> 95983 bytes dev_rf/testing/testing.py | 83 ++++++++++ 5 files changed, 333 insertions(+) create mode 100644 dev_rf/testing/logger/logger_config.cfg create mode 100644 dev_rf/testing/logger_cfg.py create mode 100644 dev_rf/testing/mapreduce_big_data.py create mode 100644 dev_rf/testing/resultados_map_reduce.jpeg create mode 100644 dev_rf/testing/testing.py diff --git a/dev_rf/testing/logger/logger_config.cfg b/dev_rf/testing/logger/logger_config.cfg new file mode 100644 index 0000000..8e1a3b5 --- /dev/null +++ b/dev_rf/testing/logger/logger_config.cfg @@ -0,0 +1,40 @@ +# Archivo de configuracion para un logger +[loggers] +keys = root + +[handlers] +keys = consoleHandler, timedRotatingFileHandler + +[formatters] +keys = logerFormatter, timedRotatingFormatter + +[logger_root] +level = INFO +handlers = consoleHandler, timedRotatingFileHandler +qualname = root + +# Parametros para el logger en formato de consola +[handler_consoleHandler] +class = StreamHandler +level = INFO +formatter = logerFormatter +args = (sys.stdout) + +# Parametros para el logger en el archivo .log +[handler_timedRotatingFileHandler] +class = handlers.TimedRotatingFileHandler +level = INFO +formatter = timedRotatingFormatter +# El parametro 'W0' indica que se logeara cada 7 dias los lunes. +args=('./dev_jb/logs/logs.log', 'W0', 1, 1) + +# Formato del mensaje para la consola +[formatter_logerFormatter] +format = %(asctime)s:%(levelname)s:%(message)s +datefmt = "%d/%m/%Y" + +# Formato de mensaje para el archivo .log +[formatter_timedRotatingFormatter] +format = %(asctime)s:%(levelname)s:%(message)s +datefmt = "%d/%m/%Y" +class = logging.Formatter \ No newline at end of file diff --git a/dev_rf/testing/logger_cfg.py b/dev_rf/testing/logger_cfg.py new file mode 100644 index 0000000..e21856b --- /dev/null +++ b/dev_rf/testing/logger_cfg.py @@ -0,0 +1,22 @@ +# Modulos # +from pathlib import Path +import logging +from logging import config + +# Path donde se encuentra el archivo de configuracion .cfg +path_file = 'D:\Alkemy\OT302-python\dev_jb\big_data\logger' + +# Nombre del archivo de configuracion .cfg +filename_cfg = "logger_config.cfg" + + +def logger_comfig(): + # Genera el path de configuracion + log_config_path = Path(path_file, filename_cfg) + + # Configura el logger + config.fileConfig(log_config_path) + logger = logging.getLogger('log') + + # Devuelve el logger + return logger \ No newline at end of file diff --git a/dev_rf/testing/mapreduce_big_data.py b/dev_rf/testing/mapreduce_big_data.py new file mode 100644 index 0000000..bbbb305 --- /dev/null +++ b/dev_rf/testing/mapreduce_big_data.py @@ -0,0 +1,188 @@ +# Modulos # +import xml.etree.ElementTree as ET +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +from datetime import datetime, timedelta +from pathlib import Path +from collections import defaultdict + +# Variables a utilizar +# file_path = r"/Users/jeremy/Code/big_data/Stack Overflow 11-2010/112010 Meta Stack Overflow" +file_path = r"/Users/rodri/OneDrive/Documentos/Data Science/Courses/Alkemy/112010 Meta Stack Overflow" +file_name = "posts.xml" +file_name2 = "comments.xml" + +""" +Funcion: mapea el archivo .xml dejando una lista para cada consigna con los datos listos para hacer un reduce +Args: + file_path (str, obligatorio): path donde se encuentran almacendaos los archivos + file_name (str, obligatorio): nombre del archivo post.xml + file_name2 (str, obligatorio): nombre del archivo comments.xml +Return: + list_tags: lista con todos los tags de los post con respuestas aceptadas + list_relation: lista de 2 coordenadas con los score y cantidad de palabras por pregunta de un post + list_answer_delay: lista de 3 coordenadas con el id del post, la fercha de creacion del post y la fecha de creacion + del primer comentario +""" + + +def mapper(file_path: str, file_name: str, file_name2: str): + """ + Funcion: mapea el archivo comments.xml devolviendo un diccionario con el id del post y la fecha de careacion de todos los + comentarios relacionado al post + Args: + file_path (str, obligatorio): path donde se encuentran almacendaos los archivos + file_name (str, obligatorio): nombre del archivo comments.xml + Return: + list_end_time: lista de 2 coordenadas con el id del post y la fechad ecereacion del comentario + """ + + def mapper_comments(file_path: str, file_name: str): + # Genera el path donde se encuentra el archivo comments.xml + xml_file = Path(file_path, file_name) + + # Crea el objeto tree + tree = ET.parse(xml_file) + + # Genera el elemento root + root = tree.getroot() + + # Lista que se utilizara en el proceso + list_end_time = [] + + # Itera sobre las filas del .xml para obtener los datos del mappeo + for row in root.iter("row"): + # Genera el objeto de treae la columna deseada + cursor_row = row.attrib + + # Guarda el postId + post_id = cursor_row.get("PostId") + + # Guarda el tiempo de creacion del comentario + end_time = cursor_row.get("CreationDate") + + # Genera el array de 2 coordenadas con el postId y el tiempo de creacion del comentario + list_end_time.append((post_id, end_time)) + + # Devuele la lista + return list_end_time + + """ + Funcion: deja una lista con el primer comentario de cada id de post + Args: + list_mapped (list, obligatorio)): lista mapeada obteniada de la funcion de mapeo + Return: + list_comment: devuelve la lista con el id del post relacionado al comentatio y la fecha del primer comentario + """ + + def reduce_comments(list_mapped: list): + # Crea los diccionarios a utilizar + arrange = defaultdict(list) + list_comment = defaultdict(list) + + # Iterea sobre la lista recibida para genrar una diccionario con key unica referida al id + # y los calores de todos los creation time referidos a ese id + for id, time in list_mapped: + arrange[id].append(time) + + # Itera sobre le diccionario previamente cereado para encontrar el valor minimo de cada creation time y + # gurdarlo en un nuveo diccionario + for id, times in arrange.items(): + list_comment[id].append(min(times)) + + # Devuelve el listado final + return list_comment + + # Genera el path donde se encuentra el archivo posts.xml + xml_file = Path(file_path, file_name) + + # Crea el objeto tree + tree = ET.parse(xml_file) + + # Genera el elemento root + root = tree.getroot() + + # Listas y diccionarios que se utilizaran en el proceso + list_tags = [] + list_relation = [] + list_answer_delay = [] + list_start_time = {} + aux_answer_delay = defaultdict(list) + + # Itera sobre las filas del .xml para obtener los datos del mappeo + for row in root.iter("row"): + # Genera el objeto de treae la columna deseada + cursor_row = row.attrib + + # Accede solo a los posts que sean de tipo 'question' + if cursor_row.get("PostTypeId") == "1": + + # Cuenta la contidad de palabras que hay en el texto del post + body_w_count = len(cursor_row.get("Body").replace("<", "").replace(">", "").strip().split(" ")) + + # Guarda el score de post + post_score = cursor_row.get("Score") + + # Crea un array de 2 coordenadas por posicion que almacena los datos para relacionar el score con + # la cantidad de palabras en el post + list_relation.append([int(post_score), body_w_count]) + + # Gurda la fecha de creacion del post + ceration_time = cursor_row.get("CreationDate") + + # Revisa que el post tenga una respuesta aceptada + if cursor_row.get("AcceptedAnswerId") != None: + # Itera sobre los tags un post para agregarlos a una lista total + for tag in cursor_row.get("Tags").replace("<", "").replace(">", " ").strip().split(" "): + list_tags.append(tag) + + # Toma las columnas que son Answer + else: + # Guarda el id del post + question_id = cursor_row.get("Id") + # Gurda el el tiempo de creacion del post en una lista referenciado por su id de post + list_start_time[question_id] = ceration_time + + # Obtiene los datos de tiempo de creacion del primer comentario refereido a cada id + aux_answer_delay = reduce_comments(mapper_comments(file_path, file_name2)) + + # Guarda los id que no tienen un post asignado + to_delete = {x: aux_answer_delay[x] for x in set(aux_answer_delay) - set(list_start_time)} + + # Elimina los comentarios que no tengan un post al que ser referidos + for key in to_delete.keys(): + aux_answer_delay.pop(key) + + # Genera un a lista con el id, la fecha de creacion del post y del comentario + for id, values in aux_answer_delay.items(): + list_answer_delay.append([key, values[0], list_start_time[id]]) + + # Devueleve las listas con los datos finales mapeados + return list_tags, list_relation, list_answer_delay + + +# Desempaqueta las listas +list_tags, list_relation, list_answer_delay = mapper(file_path, file_name, file_name2) + +# Top 10 tags con mayores respuestas acetadas +print(pd.Series(list_tags).value_counts().head(10)) +print("\n") + +# Correlacion entre cantidad de palabras en un post y su score +df_relation = pd.DataFrame(data=np.array(list_relation), columns=["score", "body"]) +print(df_relation.corr()) +print("\n") + +# Grafico de la relacion +plt.scatter(x=df_relation["score"], y=df_relation["body"]) + +# Demora de respuesta promedio en posts +df_answer_delay = pd.DataFrame(data=np.array(list_answer_delay), columns=["id", "end_time", "start_time"]) +df_answer_delay["end_time"] = df_answer_delay["end_time"].apply(lambda x: datetime.strptime(x, "%Y-%m-%dT%H:%M:%S.%f")) +df_answer_delay["start_time"] = df_answer_delay["start_time"].apply( + lambda x: datetime.strptime(x, "%Y-%m-%dT%H:%M:%S.%f") +) +df_answer_delay["diff_time"] = df_answer_delay["end_time"] - df_answer_delay["start_time"] +avg_answer_time = np.average(df_answer_delay["diff_time"].dt.total_seconds()) +print(str(timedelta(seconds=avg_answer_time))) diff --git a/dev_rf/testing/resultados_map_reduce.jpeg b/dev_rf/testing/resultados_map_reduce.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9cb251f3444fd87fd4830ed734a06ad63911036e GIT binary patch literal 95983 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 JIN1Nc2>`8}<=y}Q literal 0 HcmV?d00001 diff --git a/dev_rf/testing/testing.py b/dev_rf/testing/testing.py new file mode 100644 index 0000000..3ef86d0 --- /dev/null +++ b/dev_rf/testing/testing.py @@ -0,0 +1,83 @@ +# This file is going to be used to test Jeremy's mapreduce script. +# The objectives of the MapReduce are: +# - Top 10 tags con mayores respuestas aceptadas +# - Relación entre cantidad de palabras en un post y su puntaje +# - Demora de respuesta promedio en posts + +# Tests have been done to what can be tested. The final results are outside a function +# so they cannot be tested in a separate file. + +import datetime + +# Import all functions from map_reduce +from mapreduce_big_data import mapper + +# path and file names +file_path = r"/Users/rodri/OneDrive/Documentos/Data Science/Courses/Alkemy/112010 Meta Stack Overflow" +file_name = "posts.xml" +file_name2 = "comments.xml" + + +def test_mapper_types(): + """According to Jeremy's documentation the mapper function returns 3 lists. + + This Function tests that the mapper function returns the 3 lists. + If the assertion fails it is going to raise an AssertionError with the corresponding message""" + + list_tags, list_relation, list_answer_delay = mapper( + file_path=file_path, file_name=file_name, file_name2=file_name2 + ) + assert isinstance(list_tags, list), "A list is expected for the tags" + assert isinstance(list_relation, list), "A list is expected for the relation" + assert isinstance(list_answer_delay, list), "A list is expected for the answer delay" + + +def test_correct_elements_into_lists(): + """According to Jeremy's documentation the lists that are returned from the mapper function + have the following characteristics: + - list_tags: list of str with the different tags + - list_relation: list with 2 coordinates [score, post_quantity_of_words] + - list_answer_delay: list with 3 coordinates [post_id, creation_date_post, creation_date_first_comment] + + This function tests that the lists have the characteristics that are mentioned above + """ + list_tags, list_relation, list_answer_delay = mapper( + file_path=file_path, file_name=file_name, file_name2=file_name2 + ) + + # Assert that all tags are of type string + assert all(map(lambda x: isinstance(x, str), list_tags)), "Not all tags are of type str" + + # Assert that all list_relation elements are a list with 2 integers + for coordinates in list_relation: + # Assert that coordinates is a list + assert isinstance(coordinates, list), "Not all coordinates are lists" + # Assert that it has 2 elements + assert len(coordinates) == 2, "There are coordinates with more than 2 elements" + + # Assert that every coordinate is an integer + for coordinate in coordinates: + assert isinstance(coordinate, int), "Coordinates elements are not of type int" + + # Assert that all list_answer_delay have 3 elements and that post_id can be transformed to int + # and that creation_date_post, creation_date_first_comment can be transformed to datetime + for coordinates in list_answer_delay: + # Assert that coordinates is a list + assert isinstance(coordinates, list), "Not all coordinates are lists" + # Assert that it has 3 elements + assert len(coordinates) == 3, "There are coordinates with more than 3 elements" + + # Assert for every coordinate + + # post_id can be transformed to int + assert isinstance(int(coordinates[0]), int) + # creation_date_post can be transformed to a datetime.datetime object + assert isinstance(datetime.datetime.fromisoformat(coordinates[1]), datetime.datetime) + # creation_date_first_comment can be transformed to a datetime.datetime object + assert isinstance(datetime.datetime.fromisoformat(coordinates[2]), datetime.datetime) + + +test_mapper_types() +test_correct_elements_into_lists() + +# All test pass From 8f3d8bb45a5f81001e58872d9d9ca426c3a715fb Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Mon, 17 Oct 2022 19:18:56 +0200 Subject: [PATCH 3/3] OT302-135 - Documenting the tests --- dev_rf/testing/testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_rf/testing/testing.py b/dev_rf/testing/testing.py index 3ef86d0..c7987b0 100644 --- a/dev_rf/testing/testing.py +++ b/dev_rf/testing/testing.py @@ -5,7 +5,7 @@ # - Demora de respuesta promedio en posts # Tests have been done to what can be tested. The final results are outside a function -# so they cannot be tested in a separate file. +# so they cannot be tested in a separate file.. import datetime