Back to Home

Notebook Market Analysis Using Python

DataMining · Notebook · Price · Notebook · Price · Python

Notebook Market Analysis Using Python

    Introduction



    In this article, I will talk about the state of today's Russian laptop market. We will conduct all analytics using python code. I think it will be useful both for those who are looking for a laptop, and for those who want to practice writing in python.

    Let's start



    diy-03-425 [1]For analysis, we need a data set, unfortunately I could not find web services at Russian online laptop stores, so I had to download the price list of one of them (I will not name it) and get prices and main parameters out of it ( in my opinion, these are: processor frequency, monitor diagonal, RAM size, hard disk size and the amount of memory on the video card). Then I did some analysis on the following issues:

    1. The average cost of a laptop
    2. Average hardware on laptops
    3. The most expensive / cheapest laptop configuration
    4. Which of the configuration parameters most affects its price
    5. Price prediction of a specified configuration
    6. Graph of distribution of configurations and prices


    Lets code



    I saved the price list that I managed to get in CSV format, to work with it you need to connect the csv module:

    import  csv
    import  re
    import  random We



    also connect the module for working with random numbers and regular expressions, which we will need later.

    Next, create a method for reading and receiving laptops:

    def  get_notebooks ():
        reader = csv.reader (open ('data.csv'), delimiter = ';', quotechar = '|')
        return  filter ( lambda  x: x! = None, map (create_notebook, reader))



    everything is simple here, we read on the data.csv data file and filter by the result of the create_notebook function, because not all positions in the price are laptops, but by the way, it is:

    def  create_notebook (raw):
        try :
            notebook = Notebook ()
            notebook.vendor = raw [0] .split ('') [0]
            notebook.model = raw [ 0] .split ('') [1]
            notebook.cpu = getFloat (r "(\ d +) \, (\ d +) \ s \ Г", raw [0] .split ('/') [0])
            notebook.monitor = getFloat (r "(\ d +) \. (\ d +) \ ''", raw [0] .split ('/') [1])
            notebook.ram = getInt (r "(\ d +) \ Mb ", raw [0] .split ('/') [2])
            notebook.hdd = getInt (r" (\ d +) Gb ", raw [0] .split ('/'

            notebook.price = getInt (r "(\ d +) \ s \ rub.", raw [1])
            return  notebook
        except  Exception , e:
            return  None



    As you can see, I decided not to pay attention to the vendor, model and processor type (here, of course, not everything is so simple, but nonetheless), and also - in this method my custom helper functions are present:

    def  getFloat (regex, raw):
        m = re.search (regex, raw) .groups ()
        return  float (m [0] + '.' + m [1])

    def  getInt (regex, raw):
        m = re.search (regex, raw) .groups ()
        return  int (m [0])



    I want to note that writing for python is best done in the style of data sets, rather than OOP structures, due to the fact that the language is more suited to this style, however, to bring some order in our domain area (laptops), I introduced a class like you may have noticed above (notebook = Notebook ())

    class  Notebook:
       pass



    Great, now we have a structure in memory and it is ready for analysis ( 2005 different configurations and their cost ), so let's start:

    Average laptop cost:

    def  get_avg_price () :
        print  sum ([n.price  for  n  in  get_notebooks ()]) / len (get_notebooks ()) We execute the



    code and see that 1K $, as the standard for computers tera still valid:

    >> get_avg_price ()
    34574



    Average hardware parameters on laptops

    def  get_avg_parameters ():
        print  “cpu {0}”. format (sum ([n.cpu  for  n  in  get_notebooks ()]) / len (get_notebooks ()))
        print  “ monitor {0} ". format (sum ([n.monitor  for  n  in  get_notebooks ()]) / len (get_notebooks ()))
        print  " ram {0} ". format (sum ([n.ram  for  n  in  get_notebooks ()]) / len (get_notebooks ()))
        print  “hdd {0}”. format (sum ([n.hdd  for  n  in  get_notebooks ()]) / len (get_notebooks ()))
        print "Video {0}". Format (sum ([n.video  for  n  in  get_notebooks ()]) / len (get_notebooks ()))



    Yes, and we have an averaged configuration in our hands:

    >> get_avg_parameters ()
    cpu 2.0460798005
    monitor 14.6333167082
    ram 2448
    hdd 243
    video 289



    The most expensive / cheapest laptop configuration:

    The functions are identical, except for the functions min / max

    def  get_max_priced_notebook ():
        maxprice = max ([n.price  for  n  in  get_notebooks ()])
        maxconfig = filter ( lambda  x : x.price == maxprice, get_notebooks ()) [0]
        print  "cpu {0}". format (maxconfig.cpu)
        print  "monitor {0}". format (maxconfig.monitor)
        print  "ram {0}". format (maxconfig.ram)
        print  "hdd {0}". format (maxconfig.hdd)
        print  "video {0}". format (maxconfig.video)
        print  "price {0}". format (maxconfig.price)



    >> get_max_priced_notebook ()
    cpu 2.26
    monitor 18.4
    ram 4096
    hdd 500
    video 1024
    price 181660



    >> get_min_priced_notebook ()
    cpu 1.6
    monitor 8.9
    ram 512
    hdd 8
    video 128
    price 8090



    Which of the configuration parameters most affects its price

    It would be very interesting to find out which of the configuration parameters we pay the most money for. Having estimated, I suggested that most likely this is the diagonal of the monitor and the frequency of the processor, well, I think that it’s worth checking this.

    To begin with, our set of configuration parameters should be slightly modified. Due to the fact that the units of various parameters are different in their order, we need to bring them to the same denominator, i.e. normalize them. So     here we go :

    def  normalized_set_of_notebooks ():
    notebooks = get_notebooks ()
        cpu = max ([n.cpu  for  n  in  notebooks])
        monitor = max ([n.monitor  for  n  in  notebooks])
        ram = max ([n.ram  for  n  in  notebooks])
        hdd = max ([n.hdd  for  n  in  notebooks])
        video = max ([n.video  for  n  in  notebooks])
        rows = map ( lambda  n: [ n.cpu / cpu, n.monitor / monitor, float (n.ram) / ram, float (n.hdd) / hdd, float (n.video) / video, n.price], notebooks)
        return  rows



    In this function, I find the maximum values ​​for each of the parameters, after which I form the resulting list of laptops, in which each of the parameters is presented as a coefficient (its value will range from 0 to 1), showing the ratio of its parameter to the maximum value in the set, to For example, a memory in 2048Mb will give the configuration a coefficient of ram = 0.5 (2048/4056).

    The contribution of each of the parameters we will consider in rubles, for clarity, we will store these weights in the set:

    #cpu, monitor, ram, hdd, video
    koes = [0, 0, 0, 0, 0]



    I propose to calculate these coefficients for each configuration, and then determine the average value of all coefficients, which will give us averaged data on the weight of each of the configuration items.

    def analyze_params (parameters):
        koeshistory = []
        # our notebooks
        notes = normalized_set_of_notebooks ()
        for  i  in  range (len (notes)):
            koes = [0, 0, 0, 0, 0]
            # set the coefficients
            set_koes (notes [i] , koes)
            # save the coefficient history
            koeshistory.extend (koes)
            # show the progress
            if  (i% 100 == 0):
                print  i
                print  koes



    How will we set the coefficients for each configuration item? My way is this:

    • we need to randomly increase or decrease the value of one of the coefficients
    • then analyze whether we are close to the price for the configuration, when multiplying the parameter vector by the coefficient vector (I recall that in our case it is rubles)
    • if the approximation has taken place, we repeat this action, if not, then cancel it
    • repeat this order to the extent that we do not get closer to our price with the accuracy we set


    Here is the implementation of this algorithm:

    def  set_koes (note, koes, error = 500):
        price = get_price (note, koes)
        lasterror = abs (note [5] - price)
        while  (lasterror> error):
            k = random.randint (0 , 4)
            # change the coefficient
            inc = (random.random () * 2 - 1) * (error * (1 - error / lasterror))
            koes [k] + = inc
            # do not let the coefficient become less than zero
            if  (koes [k ] <0): koes [k] = 0
            # we get the price when taking into account the coefficients
            price = get_price (note, koes)
            # we get the current error
            curerror = abs (note [5] - price)
            # we check whether we are close to the price indicated in the price list
            if (lasterror <curerror):
                koes [k] - = inc
            else :
                lasterror = curerror



    inc - a variable responsible for increasing / decreasing the coefficient, the method of calculating it is explained by the fact that this value should be the larger, the greater the difference in error, for quick and more accurate approximation to the desired result.

    Multiplication of vectors to get the price is as follows:

    def  get_price (note, koes):
        return  sum ([note [i] * koes [i]  for  i  in  range (5)])



    It's time to analyze:

    >> analyze_params ()
    cpu , monitor, ram, hdd, video

    [15455.60675667684, 20980.560483811361, 12782.535270304281, 17819.904629585861, 14677.889529808042]



    Данный набор мы получили, благодаря усреднению коэффициентов, полученных для каждой из конфигураций:

    def get_avg_koes(koeshistory):
        koes = [0, 0, 0, 0, 0]
        for row in koeshistory:
            for i in range(5):
                koes[i] += koeshistory[i]
        for i in range(5):
            koes[i] /= len(koeshistory)
        return koes



    Итак, у нас получился желаемый набор, что же мы можем сказать из этих цифр, а можем мы составить рейтинг параметров:

    1. Диагональ монитора
    2. Объем жесткого диска
    3. Частота процессора
    4. Объем видео-карточки
    5. RAM size


    I would like to note that this is far from an ideal option, and you may get different results, however, my assumption that the processor frequency and display diagonal are the most important parameters in the configuration was partially confirmed.

    Predicting the price of the specified configuration It

    would be cool if we had such a rich data set to be able to predict the price of a given configuration. This is what we will do.

    First, we will convert our collection of laptops to a list:

    def  get_notebooks_list ():
        return  map ( lambda  n: [n.cpu, n.monitor, n.ram, n.hdd, n.video, n.price], get_notebooks ())



    Next, we need a function that can determine the distance between two vectors, a good option I see is the Euclidean distance function:

    def  euclidean (v1, v2):
        d = 0.0
        for  i  in  range (len (v1)):
            d + = (v1 [i] - v2 [i]) ** 2;
        return  math.sqrt (d)



    The root of the sum of the squares of the differences quite clearly and effectively shows us how much one vector is different from another. What is this feature useful for us? It's simple, when we get a vector with the parameters we are interested in, we will go over the entire collection of our set and find the nearest neighbor, and we already know its value, great! Here's how we do it:

    def  getdistances (data, vec1):
        distancelist = []
        for  i  in  range (len (data)):
            vec2 = data [i]
            distancelist.append ((euclidean (vec1, vec2), i))
        distancelist.sort ()
        return  distancelist



    Next, you can complicate the task a bit, as well as the accuracy of the data provided. To do this, we introduce a function using the classification by the method of k weighted nearest neighbors :

    Kweighted nearest neighbors is a metric classification algorithm based on assessing the similarity of objects. The classified object belongs to the class to which the objects of the training sample closest to it belong .


    Well, take the average value among a number of nearest neighbors, which will negate the influence of vendor prices or configuration specificity:

    def  knnestimate (data, vec1, k = 3):
        dlist = getdistances (data, vec1)
        avg = 0.0
        for  i  in  range (k):
            idx = dlist [i] [1]
            avg + = data [idx] [5]
        avg / = k
        return  avg



    * the last 3 algorithms are taken from Segeran Toby 's book “Programming the collective mind”

    And what do we get:

    > > knnestimate (get_notebooks_list (), [2.4, 17, 3062, 250, 512])
    31521.0

    >> knnestimate (get_notebooks_list (), [2.0, 15, 2048, 160, 256])
    27259.0
    >> knnestimate (get_notebooks_list (), [2.0, 15, 2048, 160, 128])
    20848.0 The



    prices are market and this is quite enough, although we absolutely do not take into account in this implementation, for example, processor frequency and monitor diagonal (for this we need to add to the function of comparing the vectors of their weight, which we calculated in the previous paragraph) The

    graph of the distribution of configurations and prices

    I would like to embrace the entire distribution pattern, i.e. draw the distribution of configurations and prices in the market. Ok, let's do it.

    First you need to put the matplotlib library . Next, connect it to our project:

    from  pylab  import  *



    We also need to create two data sets, for the abscissa and ordinate:

    def power_of_notebooks_config ():
        return  map ( lambda  x: x [0] * x [1] * x [2] * x [3] * x [4], normalized_set_of_notebooks ())
    def  config_prices ():
        return  map ( lambda  x: x [5], normalized_set_of_notebooks ())



    And the function in which we plot the distribution:

    def  draw_market ():
        plot (config_prices (), power_of_notebooks_config (), 'bo', linewidth = 1.0)

        xlabel ('price (Rub)' )
        ylabel ('config_power')
        title ('Russian Notebooks Market')
        grid (True)
        show ()



    And what do we get:

    notes

    In conclusion



    So, we managed to conduct a small analysis of the Russian laptop market, as well as play a little with python.

    The source code of the project is available at:

    http://code.google.com/p/runm/source/checkout

    I apologize for the slightly important syntax highlighting, my engine ( pygments ) did not want to be perceived by the hubr.

    Read Next