0 ? $c / $i : 0.0; $pctr = $pi > 0 ? $pc / $pi : 0.0; $pos = isset( $now['pos'] ) ? (float) $now['pos'] : null; $ppos = isset( $prev['pos'] ) ? (float) $prev['pos'] : null; $reliable = null !== $pos && null !== $ppos && $i >= self::POSITION_MIN_IMPRESSIONS && $pi >= self::POSITION_MIN_IMPRESSIONS; $out = array( 'dc' => Rules::change( $c, $pc ), 'di' => Rules::change( $i, $pi ), 'ctr' => $ctr, 'pctr' => $pctr, 'dpts' => ( $ctr - $pctr ) * 100, 'dctr' => Rules::change( $ctr, $pctr ), 'now_pos' => $pos, 'prev_pos' => $ppos, 'dpos' => $reliable ? round( $pos - $ppos, 1 ) : null, 'reliable' => $reliable, 'pattern' => 'mixed', 'ctr_also' => false, 'top' => null, 'top_share' => 0, 'vanished' => array( 'count' => 0, 'impr' => 0, ), 'slipped' => array(), 'snippet' => array(), 'rows' => array(), ); // Keyword level: where impressions and clicks went. $impr_lost = 0.0; $click_lost = 0.0; $slipped = array(); $snippet = array(); foreach ( $rows as $row ) { $impr_lost += max( 0, $row['pi'] - $row['i'] ); $click_lost += max( 0, $row['pc'] - $row['c'] ); if ( $row['pi'] >= 20 && 0.0 === (float) $row['i'] ) { ++$out['vanished']['count']; $out['vanished']['impr'] += $row['pi']; } if ( $row['pi'] >= 50 && $row['i'] > 0 && $row['pos'] - $row['ppos'] >= 3 ) { $slipped[] = $row; } if ( $row['pi'] >= 100 && $row['i'] >= 0.8 * $row['pi'] && $row['pc'] - $row['c'] >= 5 && $row['pi'] > 0 && $row['i'] > 0 && ( $row['c'] / $row['i'] ) < 0.7 * ( $row['pc'] / $row['pi'] ) ) { $snippet[] = $row; } } $by_impr = $rows; usort( $by_impr, static function ( $a, $b ) { return ( $b['pi'] - $b['i'] ) <=> ( $a['pi'] - $a['i'] ); } ); $by_click = $rows; usort( $by_click, static function ( $a, $b ) { return ( $b['pc'] - $b['c'] ) <=> ( $a['pc'] - $a['c'] ); } ); $lost_by_slip = 0.0; foreach ( $slipped as $row ) { $lost_by_slip += max( 0, $row['pc'] - $row['c'] ); } $sort_lost = static function ( $a, $b ) { return ( $b['pc'] - $b['c'] ) <=> ( $a['pc'] - $a['c'] ); }; usort( $slipped, $sort_lost ); usort( $snippet, $sort_lost ); $out['slipped'] = array_slice( $slipped, 0, 3 ); $out['snippet'] = array_slice( $snippet, 0, 3 ); // The keyword that explains the most: by impressions when visibility fell, else by clicks. $visibility = null !== $out['di'] && $out['di'] <= -15; $lead = $visibility ? ( $by_impr ? $by_impr[0] : null ) : ( $by_click ? $by_click[0] : null ); if ( $lead && ( $visibility ? $lead['pi'] > $lead['i'] : $lead['pc'] > $lead['c'] ) ) { $out['top'] = $lead; $total = $visibility ? max( $impr_lost, $pi - $i ) : max( $click_lost, $pc - $c ); $out['top_share'] = $total > 0 ? round( ( $visibility ? $lead['pi'] - $lead['i'] : $lead['pc'] - $lead['c'] ) / $total * 100 ) : 0; } // Evidence rows: the biggest losers by clicks, then by impressions, without repeats. $keep = array(); foreach ( array_merge( array_slice( $by_click, 0, 5 ), array_slice( $by_impr, 0, 5 ) ) as $row ) { if ( ( $row['pc'] > $row['c'] || $row['pi'] > $row['i'] ) && ! isset( $keep[ $row['key'] ] ) && count( $keep ) < self::ROWS ) { $keep[ $row['key'] ] = $row; } } $out['rows'] = array_values( $keep ); // Pattern, in the precedence order listed on the class. $dpos = $out['dpos']; if ( ( null !== $dpos && $dpos >= 1.0 ) || ( count( $slipped ) >= 2 && $click_lost > 0 && $lost_by_slip / $click_lost >= 0.4 ) ) { $out['pattern'] = 'ranking'; } elseif ( $visibility && null !== $dpos && $dpos <= -1.0 ) { $out['pattern'] = 'lost_searches'; } elseif ( $visibility ) { $out['pattern'] = 'demand'; } elseif ( ( null === $out['di'] || $out['di'] > -10 ) && null !== $out['dctr'] && $out['dctr'] <= -15 && $i >= 500 ) { $out['pattern'] = 'snippet'; } $out['ctr_also'] = 'snippet' !== $out['pattern'] && null !== $out['dctr'] && $out['dctr'] <= -20 && $out['dpts'] <= -0.3 && $i >= 500; return $out; } /** * How much data stands behind a change. * * @param float $before Main number before (clicks, leads or events). * @param array $levels [strong, moderate] floors. * @return string strong|moderate|early */ public static function confidence( $before, array $levels ) { if ( $before >= $levels[0] ) { return 'strong'; } return $before >= $levels[1] ? 'moderate' : 'early'; } }