BAS ASSIGNMENT #1

pdf

School

The University of Tennessee, Knoxville *

*We aren’t endorsed by this school

Course

211

Subject

Computer Science

Date

Feb 20, 2024

Type

pdf

Pages

15

Uploaded by carterjones2524

Report
BAS 320 - Assignment 1 - Working with R CARTER JONES Response: Analytics is the best major at UT! getwd () #This should output the path to your BAS folder; change with Session, Set Working Directory if not ## [1] "C:/Users/carte/Documents/320 DOCS" load ( "BAS320Assignment1.RData" ) library (regclass) ## Loading required package: bestglm ## Loading required package: leaps ## Loading required package: VGAM ## Loading required package: stats4 ## Loading required package: splines ## Loading required package: rpart ## Loading required package: randomForest ## randomForest 4.7-1.1 ## Type rfNews() to see new features/changes/bug fixes. ## Important regclass change from 1.3: ## All functions that had a . in the name now have an _ ## all.correlations -> all_correlations, cor.demo -> cor_demo, etc. Question 1: R as a calculator Translate into R syntax the following mathematical expressions. Have the answers be “printed to the screen” (i.e., don’t left -arrow them to anything) when it is knitted. Note: you can mouse over the equations to see them, or you can head to the Canvas page to see what they look like. If you only see half an equation, click the double up arrows on the far right of the equation to collapse it, then click again to expand it.
(8 − 2 1 + 4 ) (3 + 2 5 2 ) #about 23.4 ( 8 - ( 2 / ( 1 + 4 ))) * ( 3 + ( 2 / 5 ^ 2 )) ## [1] 23.408 8 + 3 8/5 − 2 1/3 2 3 + 3 2/3 + 4 5/7 #about 8.355 8 + (( 3 ^ ( 8 / 5 ) - ( 2 ^ 1 / 3 )) / ( 2 ^ 3 + 2 ^ ( 2 / 3 ) + 4 ^ ( 5 / 7 ))) ## [1] 8.418014 |√2 − 300𝑒 −2(8−6) | + log 10 294 − ln320 − 12 #about -11.22 abs ( sqrt ( 2 ) - 300 * ( exp ( - 2 * ( 8 - 6 )))) + log10 ( 294 ) - log ( 320 ) - 12 ## [1] -11.2195 "answer with help from chatgpt" ## [1] "answer with help from chatgpt" d. When translating this equation into R, write values in R’s version of scientific notation when possible 8 × 10 −2 + 8.34 × 10 2 + 5.32 × 10 3 #about 6154 ( 8 * 10 ^- 2 ) + ( 8.34 * 10 ^ 2 ) + ( 5.32 * 10 ^ 3 ) ## [1] 6154.08 Question 2: Left-arrow Step 1: Define a variable named heard to equal 10. Imagine this is the number of people that have heard of a new product on day 1 (additional people hear of the product by word of mouth). Step 2: Re-define heard to be 148% its current value, minus 2, then rounded to the nearest integer. Imagine this is the number of people that have heard of the product on day 2. You should find it will go from equaling 10 on day 1 to equaling 12.8, which rounds to 13, on day 2.
Step 3: Step 2 is then repeated a total of 10 times (giving the number of people who have heard of the product on days 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) Write R code that updates the value of heard through day 12. Print to the screen the value of heard . Sanity check: between 430 and 445. Remember the phrase “print to the screen” just means ensuring the command does not have a left-arrow so that the results get output to the Console and are “knitted” into the document. heard <- 10 heard <- round ((heard * 1.48 ) - 2 , digits= 0 ) for (i in 1 : 10 ) {heard <- round ((heard * 1.48 ) - 2 , digits= 0 )} heard ## [1] 435 Question 3: vector creation a. Create a categorical vector named Q3a that contains the elements sun, snow, sun, clouds, clouds, sun, sun, clouds. The word “snow” will have a funny outline when typing it. Provide a frequency table of the elements in Q3a (you’ll need the factor and table commands here). Q3a <- factor ( c ( "sun" , "snow" , "sun" , "clouds" , "cloud" , "sun" , "sun" , "clouds" )) table (Q3a) ## Q3a ## cloud clouds snow sun ## 1 2 1 4 b. Create a numeric vector named Q3b that contains the elements 204, 174, 14, -16, 93, 32, 1. Print to the screen the average of the elements of this vector, rounded to 1 decimal place (you’ll need to use the mean and round functions here). Remember the phrase “print to the screen” just means ensuring the command does not have a left - arrow so that the results get output to the Console and are “knitted” into the document. Q3b <- c ( 204 , 174 , 14 , - 16 , 93 , 32 , 1 ) Q3b <- mean (Q3b) round (Q3b, 0 ) ## [1] 72 c. Create a numeric vector named Q3c whose elements are a regularly spaced sequence that starts at 132.3, ends at 87.3, and goes down in increments of 1.5 (you’ll need the seq command here). Print the contents of Q3c to the screen. Though not required for this homework, be sure you also know how to create a sequence that starts at 87.3, end at 132.3, and goes up by 1.5 Q3c <- seq ( 132.3 , 87.3 , by= - 1.5 ) Q3c
Your preview ends here
Eager to read complete document? Join bartleby learn and gain access to the full version
  • Access to all documents
  • Unlimited textbook solutions
  • 24/7 expert homework help
## [1] 132.3 130.8 129.3 127.8 126.3 124.8 123.3 121.8 120.3 118.8 117.3 115.8 ## [13] 114.3 112.8 111.3 109.8 108.3 106.8 105.3 103.8 102.3 100.8 99.3 97.8 ## [25] 96.3 94.8 93.3 91.8 90.3 88.8 87.3 d. Create a text vector named Q3d that consists of the nine words great, great, fair, fair, fair, average, average, average, average, repeated a total of four times. You’ll want to use the rep command, and your vector should contain a total of 36 elements. Print to the screen the contents of Q3d and show the result of running table(Q3d) to give a frequency table of its elements. Hint: although not required, try creating the text vector (the one that gets repeated 4 times) with three different rep commands. Q3d <- rep ( c ( "great" , "great" , "fair" , "fair" , "fair" , "average" , "average" , "average" , "ave rage" ), times= 4 ) Q3d ## [1] "great" "great" "fair" "fair" "fair" "average" "average" ## [8] "average" "average" "great" "great" "fair" "fair" "fair" ## [15] "average" "average" "average" "average" "great" "great" "fair" ## [22] "fair" "fair" "average" "average" "average" "average" "great" ## [29] "great" "fair" "fair" "fair" "average" "average" "average" ## [36] "average" table (Q3d) ## Q3d ## average fair great ## 16 12 8 e. Create a vector named items that contains the integer sequence from 43 down to 3. Create a vector named prices that contains the integer sequence from 10 up to 50. What’s the median of the vector produced when you multiply together the elements of items and prices ? items <- seq ( from= 43 , to= 3 , by= - 1 ) items <- 43 : 3 prices <- 10 : 50 median <- median (items * prices) median ## [1] 592 Question 4: Vectors (NFL data) Load in the NFL dataframe that is contained in the regclass library (you’ll need the library and data command to get this dataset into the environment). This data contains statistics for NFL teams from the 2002- 2012 seasons. Let’s look at the X13.OffPassYds column, which gives a team’s total number of yards gained when passing over the course of
a season. Save (left-arrow) the X13.OffPassYds column into a vector named pass , and use this vector in the following parts. For most parts, you’ll need to use the which function. library (regclass) data (NFL) pass <- NFL $ X13.OffPassYds a. Print to the screen the values in the 212th, 325th, and 326th positions of pass . pass <- NFL $ X13.OffPassYds pass[ c ( 212 , 325 , 326 )] ## [1] 4977 3683 2999 b. In what positions of pass will you find the number 2890 (there are three positions)? positions <- which (pass == 2890 , arr.ind = TRUE ) positions ## [1] 17 126 231 "chat gpt helped me on this one" ## [1] "chat gpt helped me on this one" c. Print to the screen all unique values of pass that are between 2880 and 2920 (inclusive). You’ll need to use the unique function (not discussed before; you can Google how to use it or ask ChatGPT, but it’s pretty self -explanatory). Note: there are eight elements of pass in this range, but only five are unique (i.e., only 5 values should be printed to the screen for this part). x <- pass[pass >= 2880 & pass <= 2920 ] x <- unique (x) x ## [1] 2890 2882 2916 2898 2891 d. What is the average value of all elements of pass that are less than 2320? You’ll need to use the mean function. av <- pass[pass < 2320 ] av <- mean (av) av ## [1] 2166.5 e. How many values of pass are at least 3995? You’ll need to use the length function. tnn <- pass[pass >- 3995 ] length (tnn) ## [1] 352 f. Some (in fact 75) of the elements of pass are multiple of 5. By using the %in% shortcut and seq , print to the screen all elements in pass that are multiples of 5,
sorted from smallest to largest. Note: the smallest value of pass is 1898 and largest is 5347, so choose wise to and from limits in your seq function! ting <- seq ( from= 5 , to= 5347 , by= 5 ) ting <- pass[pass %in% ting] ting <- order (ting, decreasing= FALSE ) ting ## [1] 3 10 42 44 53 33 20 47 13 34 19 37 1 39 68 5 27 48 66 45 63 31 41 70 56 ## [26] 15 46 36 43 11 18 17 58 64 62 67 51 60 22 12 14 74 24 9 55 61 32 28 71 8 ## [51] 54 26 75 69 6 40 30 16 23 57 21 29 73 72 59 2 4 35 38 65 25 49 52 7 50 g. Determine the number of elements in pass that are either 2890, 3031, 4814, 4000, 3820, or 3333. The most efficient way of doing this uses the %in% shortcut. oop <- c ( 2890 , 3031 , 4814 , 4000 , 3820 , 3333 ) blob <- pass[pass %in% oop] length (blob) ## [1] 9 h. Report the median value of pass using all elements except for the ones in positions 52 through 111. Your answer will be a little above 3361 yee <- pass[ - c ( 52 : 111 )] ye <- median (yee) ye ## [1] 3361.5 "done with help of gpt" ## [1] "done with help of gpt" Question 5: Data Frames (Spotify data) After loading in the .RData file for this assignment ( BAS320Assignment1.RData ; see chunk code near the top), you’ll see a dataframe called SPOTHIT in the global environment. This dataset contains information on 41106 songs that you can stream on Spotify (see track and artist ) along with song characteristics such as danceability , energy , valence (see https://developer.spotify.com/documentation/web-api/reference/get-audio- features for detailed definitions of these quantities). a. The class function reveals the type of objects (numeric vector, factor, data.frame, function, etc.) that are defined in R. What type of objects are SPOTHIT , SPOTHIT$artist , SPOTHIT$key , and SPOTHIT$tempo ? The output of running class will suffice. sapply ( list (SPOTHIT,SPOTHIT $ artist,SPOTHIT $ key,SPOTHIT $ tempo),class)
Your preview ends here
Eager to read complete document? Join bartleby learn and gain access to the full version
  • Access to all documents
  • Unlimited textbook solutions
  • 24/7 expert homework help
## [1] "data.frame" "character" "factor" "numeric" "i used gpt to figure out how to use sapply function so i wouldnt have to write 4 lines" ## [1] "i used gpt to figure out how to use sapply function so i wouldnt have to write 4 lines" b. What is sum total of all values in the sections column of the data? ii <- SPOTHIT suuum <- sum (ii $ sections) suuum ## [1] 430613 c. What percentage (a number between 0-1) of songs have values of tempo that are strictly less than 80 (beats per minute)? Try using length and which to count up the number of entries in the tempo column that are strictly less than 80 then dividing by the number of rows in SPOTHIT . uu <- (ii $ tempo) uu <- uu[uu < 80 ] uu <- length (uu) OO <- nrow (SPOTHIT) uu / OO ## [1] 0.0714251 d. Print to the screen the song names in rows 29200, 35271, 37263, and 40969. SPOTHIT $ track[ c ( 29200 , 35271 , 37263 , 40969 )] ## [1] "You Belong With Me" "I Knew You Were Trouble." ## [3] "Bad Blood" "Shake It Off" e. Print to the screen the first 8 entries of the target column, which say “Yes” or “No” whether the song was considered a hit. head (SPOTHIT $ target) ## [1] Yes No No No No No ## Levels: No Yes f. Use the levels function to print to the screen the levels (possible values) of the time_signature column. Then, again using the levels function, rename them be Unknown , 1/4 , 3/4 , 4/4 and 5/4 . Then, use tail to show the values of time_signature in the last 15 rows of the data. levels (SPOTHIT $ time_signature) ## [1] "0" "1" "3" "4" "5" ts <- SPOTHIT $ time_signature levels (ts) <- c ( "Unknown" , "1/4" , "3/4" , "4/4" , "5/4" ) tail (ts, 15 )
## [1] 4/4 3/4 4/4 5/4 4/4 4/4 3/4 4/4 4/4 4/4 4/4 4/4 4/4 4/4 4/4 ## Levels: Unknown 1/4 3/4 4/4 5/4 g. Which rows (1-41106) contain the most energetic songs ( energy column), at least according to Spotify’s data? What are the song titles and artists (they are actually all by the same artist)? Hint: you can use the which command to find the row numbers that have the max value of energy , then find the track and artist in those positions. Try checking out these “songs” on Spotify; they probably aren’t what you’d expect (and any analysis that tries to determine if a song is a hit or flop should probably exclude these songs). ene <- which (SPOTHIT $ energy == max (SPOTHIT $ energy)) ene <- SPOTHIT[ene, c ( "track" , "artist" )] ene ## track artist ## 17457 Endless Rain Nataural ## 17707 Dripping Rain Nataural ## 22369 Under Shelter Rain Nataural ## 22667 Pouring Rain Nataural "chatgpt helped me with this one" ## [1] "chatgpt helped me with this one" h. What value is in the “cell” located in the 10th column and 8833rd row of SPOTHIT ? SPOTHIT[ 8833 , 10 ] ## [1] 0.264 i. Print out the entirety of the 41100th row to the screen. SPOTHIT[ 41100 ,] ## track artist uri ## 41100 Sweater Weather The Neighbourhood spotify:track:2QjOHCTQ1Jl3zawyYOpxh6 ## danceability energy key loudness mode speechiness acousticness ## 41100 0.612 0.807 10 -2.81 Major 0.0336 0.0495 ## instrumentalness liveness valence tempo duration_ms time_signature ## 41100 0.0177 0.101 0.398 124.053 240400 4 ## chorus_hit sections target ## 41100 91.20552 7 Yes j. Change the name of the column named target to Hit . Provide a frequency table of the elements in the Hit column. names (SPOTHIT)[ which ( names (SPOTHIT) == "target" ) ] <- "Hit" table (SPOTHIT $ Hit)
## ## No Yes ## 20553 20553 k. (Bonus) Print to the screen the name of all artists that have a song named “Crazy” in the data. No hints will be given for this question, though you will want to reference both the track and artist columns and use which . cra <- SPOTHIT $ track craz <- SPOTHIT $ artist cra <- which (cra == "Crazy" ) cra <- craz[cra] cra ## [1] "Patsy Cline" "999" "The Manhattans" "Kenny Rogers" ## [5] "Icehouse" "Seal" "Aerosmith" "The Boys" ## [9] "Daisy Dee" "Gnarls Barkley" "Javier" "K-Ci & JoJo" "chatgpt helped me with this" ## [1] "chatgpt helped me with this" Question 6: Data Frames (online retailer) Online retailers do analytics on their customer base to learn what customers are most likely to buy what products. The data in Assignment1-OnlineSales.csv summarizes the weekly performance of items sold by an online retailer. Each row gives the characteristics of a product aggregated at the weekly level. Product - the category of the item (anonymized, but could be t-shirt, watches, rings, TVs, games, etc.) Variety - the number of different styles/models offered for that item Showings - the number of times that item was featured in a banner ad on their site that week UnitCost - the average amount of money it costs the retailer to acquire the item Markup - the factor by which the retailer marks up the UnitCost when selling GrossQuantity - the total number of units sold for that week NetQuantity - the total number of units sold for the week after taking returns into consideration Review - the average review score of the item that week (1-10) Text - encrypted item description text a. Use read.csv to get the data into R, left-arrowing the output of read.csv so that the data frame is named RETAILER . Set up the command so that it reads in text as text, then convert the Product column to a categorical variable by running a factor() command. Print to the screen the result of running summary on the dataframe. Ensure that the summary of the Product column shows a selection of its levels and frequencies.
Your preview ends here
Eager to read complete document? Join bartleby learn and gain access to the full version
  • Access to all documents
  • Unlimited textbook solutions
  • 24/7 expert homework help
Note: there have previously been isolated reports of the Assignment1-OnlineSales.csv not being downloaded correctly (opening it up in Excel or Numbers shows “Unauthorized” in a single cell). We’re unsure of the root cause, but as a last resort, instead of reading in the data file from the computer (which won’t work), you can run: RETAILER <- read.csv("https://docs.google.com/spreadsheets/d/1tPaaeqn_8dJYrQ1mQwcnvfN1IIM BttXtXzJ0k6zUxp4/export?format=csv",stringsAsFactors=TRUE) Retailer <- read.csv ( "Assignment1-OnlineSales.csv" ) Retailer $ Product <- factor (Retailer $ Product) summary (Retailer) ## Product Variety Showings UnitCost ## Category10:7593 Min. : 1.00 Min. : 0.00 Min. : 0.00 ## Category9 :7064 1st Qu.: 1.00 1st Qu.: 0.00 1st Qu.: 10.70 ## Category6 :5262 Median : 3.00 Median : 0.00 Median : 19.99 ## Category8 :4976 Mean : 10.31 Mean : 11.68 Mean : 39.24 ## Category2 :3775 3rd Qu.: 7.00 3rd Qu.: 8.00 3rd Qu.: 39.48 ## Category3 :3381 Max. :763.00 Max. :1510.00 Max. :2200.00 ## (Other) :7447 ## Markup NetQuantity GrossQuantity Review ## Min. :-19.0000 Min. : -240.0 Min. : 0.0 Min. : 1.000 ## 1st Qu.: 0.5000 1st Qu.: 2.0 1st Qu.: 3.0 1st Qu.: 7.200 ## Median : 0.6100 Median : 9.0 Median : 11.0 Median : 8.500 ## Mean : 0.5385 Mean : 111.3 Mean : 137.4 Mean : 8.097 ## 3rd Qu.: 0.6600 3rd Qu.: 53.0 3rd Qu.: 66.0 3rd Qu.: 9.600 ## Max. : 1.0000 Max. :12112.0 Max. :14859.0 Max. :10.000 ## ## Text ## Length:39498 ## Class :character ## Mode :character ## ## ## ## b. Create a column in the RETAILER dataframe named Price that tabulates the typical selling price of items sold that week. Hint: a formula for the price is: 𝑃?𝑖𝑐𝑒 = 𝑈?𝑖?𝐶???(1 + 𝑀𝑎?𝑘??) Print to the screen the result of running summary on the Price column. Sanity check: you’ll find the Mean equals 58.29 from the summary. UC <- Retailer $ UnitCost Markup <- Retailer $ Markup Retailer $ Price <- UC * ( 1 + Markup) summary (Retailer $ Price)
## Min. 1st Qu. Median Mean 3rd Qu. Max. ## -552.50 16.70 30.32 58.29 60.27 3168.00 c. Sometimes items are offered on clearance to get rid of inventory. Unfortunately, when this happens, the record-keeping messes up the value of Markup , resulting in a Price that can be negative. Print to the screen the result of running subset on RETAILER , keeping only rows that have Price less than -100 and keeping only the Product, UnitCost, Markup, and Price columns. Sanity check: ensure that only 18 rows and 4 columns (5 including the row number) are printed to the screen. If this command is incorrect and the entirety of the dataframe is printed to the screen, that’ll be excessive output and the grader will stop! The first row number will be 10964 and the last row number will be 28608. th <- subset (Retailer,Price < - 100 , select = c ( "Product" , "UnitCost" , "Markup" , "Price" )) th ## Product UnitCost Markup Price ## 10964 Category6 20.80 -5.93 -102.544 ## 11126 Category8 79.97 -3.80 -223.916 ## 16653 Category6 19.50 -7.03 -117.585 ## 16952 Category10 85.00 -2.40 -119.000 ## 17533 Category1 20.00 -9.00 -160.000 ## 17583 Category6 19.50 -11.19 -198.705 ## 18308 Category6 19.50 -7.53 -127.335 ## 18457 Category6 19.50 -9.64 -168.480 ## 19338 Category10 85.00 -3.25 -191.250 ## 19354 Category1 20.00 -19.00 -360.000 ## 19472 Category10 85.00 -7.50 -552.500 ## 19527 Category1 20.00 -19.00 -360.000 ## 19945 Category1 20.00 -9.00 -160.000 ## 24615 Category9 65.00 -2.61 -104.650 ## 25094 Category2 27.65 -4.92 -108.388 ## 27664 Category9 35.00 -4.00 -105.000 ## 28523 Category9 65.00 -4.42 -222.300 ## 28608 Category9 35.00 -4.83 -134.050 "chatgpt helped me figure out how to keep only the indicated columns" ## [1] "chatgpt helped me figure out how to keep only the indicated columns" d. Let’s “fix” the issues with Price by throwing away any rows with values 0 or less and restricting our analysis to items in product categories 1, 3, and 5. Create a subset named SUB whose rows have strictly positive values of price and whose product categories are 1, 3, or 5. Once the subset has been created, remove the Text column. Then, remove unused levels of Product (since only 3 of the 10 possible categories are being examined) using a droplevels command.
Print to the screen the number of rows and columns in the subset (you should find 7275 and 9, respectively). Hint: use the %in% shortcut when referencing the levels of Product that you want to keep. SUB <- subset (Retailer, Price > 0 & Product %in% factor ( c ( "Category1" , "Category3" , "Category5" ))) SUB <- subset (SUB, select = - Text) dim (SUB) ## [1] 7275 9 e. Create a dataframe named SORTSUB which is the result of sorting the rows in SUB by their price (from highest to lowest). Print to the screen the first 8 rows of SORTSUB using the head command (the highest price will be a little over 442). SORTSUB <- SUB[ order (SUB $ Price, decreasing = TRUE ), ] head (SORTSUB, 8 ) ## Product Variety Showings UnitCost Markup NetQuantity GrossQuantity ## 8977 Category3 1 4 264.88 0.67 6 8 ## 5875 Category3 1 14 262.09 0.66 16 31 ## 4176 Category3 1 0 260.50 0.67 1 1 ## 4086 Category3 1 0 260.25 0.67 2 2 ## 7477 Category3 1 9 261.80 0.66 7 14 ## 3477 Category3 1 0 260.00 0.67 2 3 ## 4713 Category3 1 0 260.00 0.67 1 1 ## 5681 Category3 1 0 259.53 0.67 1 1 ## Review Price ## 8977 7.4 442.3496 ## 5875 5.4 435.0694 ## 4176 10.0 435.0350 ## 4086 8.4 434.6175 ## 7477 4.3 434.5880 ## 3477 8.1 434.2000 ## 4713 9.5 434.2000 ## 5681 9.9 433.4151 f. Let’s explore some of the data in SUB . In the R chunk below: Create a histogram of the values in Price . Have the bars start at 0, go to 450, and go up in increments of 10. Hint: use the breaks= argument and use seq wisely. Create a histogram of log10 values of Price . Have the bars start at -2 (which corresponds to a price of 0.01), go up to 3 (which corresponds to a price of 1000), and go up in increments of 0.1. You’ll notice that the original distribution was very skewed, while the distribution of the logged values is more symmetric (we prefer more symmetric distributions in analytics). Create a bar plot of the values in Product . If you ran droplevels in (d) correctly, you’ll only see 3 bars (since other categories no longer appear in the data). If not,
Your preview ends here
Eager to read complete document? Join bartleby learn and gain access to the full version
  • Access to all documents
  • Unlimited textbook solutions
  • 24/7 expert homework help
you’ll see 10 bars (but the only ones with any height will be the ones for the three categories in the subset). Make a boxplot of the unit costs Bonus: Create a scatterplot showing the relationship between Price (horizontal axis) and Review (vertical axis) and add a trend line. You will need to Google / ChatGPT this one as it has not been covered yet. Looking up how to do stuff is a good skill to acquire! hist (SUB $ Price, breaks = seq ( from= 0 , to= 450 , by= 10 ),) hist ( log10 (SUB $ Price), breaks= seq ( from= - 2 , to= 3 , by= . 1 ))
barplot ( ( table (SUB $ Product)))
plot (SUB $ Price, SUB $ Review, xlab = "Price" , ylab = "Review" , main = "Scatterplot of Price vs. Review" ) abline ( lm (SUB $ Review ~ SUB $ Price), col = "red" )
Your preview ends here
Eager to read complete document? Join bartleby learn and gain access to the full version
  • Access to all documents
  • Unlimited textbook solutions
  • 24/7 expert homework help